summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCarl Hetherington <cth@carlh.net>2025-04-27 21:49:07 +0200
committerCarl Hetherington <cth@carlh.net>2025-04-28 02:31:15 +0200
commitd35e71e05c79d5343b88c887263bd4df190ad674 (patch)
treed21482ee035750a6cf6ad2232797782a427c22a5
parent789b3ba91a4444a73090a2c3a1b44cc97b488a33 (diff)
Add new Coords class to manage OpenGL coordinates and use it for the video view.
-rw-r--r--src/wx/gl_util.cc197
-rw-r--r--src/wx/gl_util.h131
-rw-r--r--src/wx/gl_video_view.cc89
-rw-r--r--src/wx/gl_video_view.h6
4 files changed, 292 insertions, 131 deletions
diff --git a/src/wx/gl_util.cc b/src/wx/gl_util.cc
index eb48ef97f..4407fb1bb 100644
--- a/src/wx/gl_util.cc
+++ b/src/wx/gl_util.cc
@@ -184,5 +184,202 @@ Texture::set(shared_ptr<const Image> image, int component)
}
+/** @param x x position in pixels where 0 is left and canvas_width is right on screen */
+static
+float
+x_pixels_to_gl(wxSize const& canvas_size, int x) {
+ return (x * 2.0f / canvas_size.GetWidth()) - 1.0f;
+}
+
+
+/** @param y y position in pixels where 0 is top and canvas_height is bottom on screen */
+static
+float
+y_pixels_to_gl(wxSize const& canvas_size, int y) {
+ return 1.0f - (y * 2.0f / canvas_size.GetHeight());
+}
+
+
+Coords::Rectangle::Rectangle(Coords& coords, int node, int index)
+ : _coords(coords)
+ , _node(node)
+ , _index(index)
+{
+ auto& c = _coords._coords;
+
+ auto offset = c.size();
+
+ c.resize(c.size() + 5 * 4);
+
+ c[offset + 2] = 0.0f;
+ c[offset + 3] = 1.0f;
+ c[offset + 4] = 1.0f;
+
+ c[offset + 7] = 0.0f;
+ c[offset + 8] = 1.0f;
+ c[offset + 9] = 0.0f;
+
+ c[offset + 12] = 0.0f;
+ c[offset + 13] = 0.0f;
+ c[offset + 14] = 0.0f;
+
+ c[offset + 17] = 0.0f;
+ c[offset + 18] = 0.0f;
+ c[offset + 19] = 1.0f;
+}
+
+
+void
+Coords::Rectangle::set(wxSize canvas_size, int x, int y, dcp::Size size)
+{
+ set(canvas_size, x, y, wxSize(size.width, size.height));
+}
+
+
+void
+Coords::Rectangle::set(wxSize canvas_size, int x, int y, wxSize size)
+{
+ auto const x1 = x_pixels_to_gl(canvas_size, x);
+ auto const y1 = y_pixels_to_gl(canvas_size, y);
+ auto const x2 = x_pixels_to_gl(canvas_size, x + size.GetWidth());
+ auto const y2 = y_pixels_to_gl(canvas_size, y + size.GetHeight());
+
+ /* The texture coordinates here have to account for the fact that when we put images into the texture OpenGL
+ * expected us to start at the lower left but we actually started at the top left. So although the
+ * top of the texture is at 1.0 we pretend it's the other way round.
+ */
+
+ auto& coords = _coords._coords;
+
+ auto const o = _node * 5;
+ DCPOMATIC_ASSERT((o + 16) < static_cast<int>(coords.size()));
+
+ // bottom right
+ coords[o] = x2;
+ coords[o + 1] = y2;
+
+ // top right
+ coords[o + 5] = x2;
+ coords[o + 6] = y1;
+
+ // top left
+ coords[o + 10] = x1;
+ coords[o + 11] = y1;
+
+ // bottom left
+ coords[o + 15] = x1;
+ coords[o + 16] = y2;
+}
+
+
+Coords::FilledRectangle::FilledRectangle(Coords& coords)
+ : Rectangle(coords, coords._nodes, coords._indices.size())
+{
+ auto& indices = coords._indices;
+ auto& nodes = coords._nodes;
+
+ indices.push_back(nodes + 0);
+ indices.push_back(nodes + 1);
+ indices.push_back(nodes + 3);
+ indices.push_back(nodes + 1);
+ indices.push_back(nodes + 2);
+ indices.push_back(nodes + 3);
+ nodes += 4;
+}
+
+
+void
+Coords::FilledRectangle::draw()
+{
+ glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, reinterpret_cast<void*>(_index * sizeof(int)));
+ check_gl_error("glDrawElements");
+}
+
+
+Coords::FilledRectangle
+Coords::add_filled_rectangle()
+{
+ DCPOMATIC_ASSERT(!_setup);
+ return FilledRectangle(*this);
+}
+
+
+Coords::OutlineRectangle::OutlineRectangle(Coords& coords)
+ : Rectangle(coords, coords._nodes, coords._indices.size())
+{
+ auto& indices = _coords._indices;
+ auto& nodes = _coords._nodes;
+
+ indices.push_back(nodes + 0);
+ indices.push_back(nodes + 1);
+ indices.push_back(nodes + 1);
+ indices.push_back(nodes + 2);
+ indices.push_back(nodes + 2);
+ indices.push_back(nodes + 3);
+ indices.push_back(nodes + 3);
+ indices.push_back(nodes + 0);
+ nodes += 4;
+}
+
+
+void
+Coords::OutlineRectangle::draw()
+{
+ glDrawElements(GL_LINES, 8, GL_UNSIGNED_INT, reinterpret_cast<void*>(_index * sizeof(int)));
+ check_gl_error("glDrawElements");
+}
+
+
+Coords::OutlineRectangle
+Coords::add_outline_rectangle()
+{
+ DCPOMATIC_ASSERT(!_setup);
+ return OutlineRectangle(*this);
+}
+
+
+void
+Coords::FilledRectangleGroup::add(Coords::FilledRectangle const& rectangle)
+{
+ if (_index == -1) {
+ _index = rectangle.index();
+ } else {
+ DCPOMATIC_ASSERT(rectangle.index() == _index + _number);
+ }
+ _number += 6;
+}
+
+
+void
+Coords::FilledRectangleGroup::draw()
+{
+ glDrawElements(GL_TRIANGLES, _number, GL_UNSIGNED_INT, reinterpret_cast<void*>(_index * sizeof(int)));
+ check_gl_error("glDrawElements");
+}
+
+
+void
+Coords::setup()
+{
+ DCPOMATIC_ASSERT(!_setup);
+
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, _indices.size() * sizeof(unsigned int), _indices.data(), GL_STATIC_DRAW);
+ check_gl_error("glBufferData");
+ glBufferData(GL_ARRAY_BUFFER, _nodes * 5 * sizeof(float), nullptr, GL_STATIC_DRAW);
+ check_gl_error("glBufferData");
+
+ _setup = true;
+}
+
+
+void
+Coords::set_array_buffer()
+{
+ DCPOMATIC_ASSERT(_setup);
+ glBufferSubData(GL_ARRAY_BUFFER, 0, _coords.size() * sizeof(float), _coords.data());
+ check_gl_error("glBufferSubData");
+}
+
+
#endif
diff --git a/src/wx/gl_util.h b/src/wx/gl_util.h
index 5daac8523..f5ea15c90 100644
--- a/src/wx/gl_util.h
+++ b/src/wx/gl_util.h
@@ -53,74 +53,7 @@ class Image;
namespace dcpomatic {
namespace gl {
-class Rectangle
-{
-public:
- Rectangle(wxSize canvas_size, float x, float y, dcp::Size size)
- : _canvas_size(canvas_size)
- {
- auto const x1 = x_pixels_to_gl(x);
- auto const y1 = y_pixels_to_gl(y);
- auto const x2 = x_pixels_to_gl(x + size.width);
- auto const y2 = y_pixels_to_gl(y + size.height);
-
- /* The texture coordinates here have to account for the fact that when we put images into the texture OpenGL
- * expected us to start at the lower left but we actually started at the top left. So although the
- * top of the texture is at 1.0 we pretend it's the other way round.
- */
-
- // bottom right
- _vertices[0] = x2;
- _vertices[1] = y2;
- _vertices[2] = 0.0f;
- _vertices[3] = 1.0f;
- _vertices[4] = 1.0f;
-
- // top right
- _vertices[5] = x2;
- _vertices[6] = y1;
- _vertices[7] = 0.0f;
- _vertices[8] = 1.0f;
- _vertices[9] = 0.0f;
-
- // top left
- _vertices[10] = x1;
- _vertices[11] = y1;
- _vertices[12] = 0.0f;
- _vertices[13] = 0.0f;
- _vertices[14] = 0.0f;
-
- // bottom left
- _vertices[15] = x1;
- _vertices[16] = y2;
- _vertices[17] = 0.0f;
- _vertices[18] = 0.0f;
- _vertices[19] = 1.0f;
- }
-
- float const * vertices() const {
- return _vertices;
- }
-
- /** @return size of data returned from vertices() in bytes */
- int const size() const {
- return sizeof(_vertices);
- }
-private:
- /* @param x x position in pixels where 0 is left and canvas_width is right on screen */
- float x_pixels_to_gl(int x) const {
- return (x * 2.0f / _canvas_size.GetWidth()) - 1.0f;
- }
-
- /* @param y y position in pixels where 0 is top and canvas_height is bottom on screen */
- float y_pixels_to_gl(int y) const {
- return 1.0f - (y * 2.0f / _canvas_size.GetHeight());
- }
-
- wxSize _canvas_size;
- float _vertices[20];
-};
extern GLuint compile_shader(GLenum type, char const* source);
extern void check_gl_error(char const* last);
@@ -149,6 +82,70 @@ private:
};
+class Coords
+{
+public:
+ class Rectangle
+ {
+ public:
+ Rectangle(Coords& coords, int node, int index);
+
+ virtual void draw() = 0;
+
+ void set(wxSize canvas_size, int x, int y, dcp::Size size);
+ void set(wxSize canvas_size, int x, int y, wxSize size);
+
+ int index() const {
+ return _index;
+ }
+
+ protected:
+ Coords& _coords;
+ int _node;
+ int _index;
+ };
+
+ class FilledRectangle : public Rectangle
+ {
+ public:
+ FilledRectangle(Coords& coords);
+ void draw() override;
+ };
+
+ class OutlineRectangle : public Rectangle
+ {
+ public:
+ OutlineRectangle(Coords& coords);
+ void draw() override;
+ };
+
+ class FilledRectangleGroup
+ {
+ public:
+ void add(FilledRectangle const& rectangle);
+ void draw();
+
+ private:
+ int _index = -1;
+ int _number = 0;
+ };
+
+ FilledRectangle add_filled_rectangle();
+ OutlineRectangle add_outline_rectangle();
+
+ void setup();
+ void set_array_buffer();
+
+private:
+ friend class Rectangle;
+
+ bool _setup = false;
+ int _nodes = 0;
+ std::vector<unsigned int> _indices;
+ std::vector<float> _coords;
+};
+
+
}
}
diff --git a/src/wx/gl_video_view.cc b/src/wx/gl_video_view.cc
index a7cdcf548..b9ccd66e5 100644
--- a/src/wx/gl_video_view.cc
+++ b/src/wx/gl_video_view.cc
@@ -200,6 +200,10 @@ GLVideoView::GLVideoView(FilmViewer* viewer, wxWindow *parent)
fragment_source
)
, _rec2020(false)
+ , _image(_coords.add_filled_rectangle())
+ , _outline_content(_coords.add_outline_rectangle())
+ , _crop_guess(_coords.add_outline_rectangle())
+ , _subtitle(_coords.add_filled_rectangle())
, _meters(parent, viewer)
{
_view.Sized.connect(boost::ref(Sized));
@@ -241,38 +245,6 @@ enum class FragmentType
};
-/* Offset and number of indices for the things in the indices array below */
-static constexpr int indices_video_texture_offset = 0;
-static constexpr int indices_video_texture_number = 6;
-static constexpr int indices_subtitle_texture_offset = indices_video_texture_offset + indices_video_texture_number;
-static constexpr int indices_subtitle_texture_number = 6;
-static constexpr int indices_outline_content_offset = indices_subtitle_texture_offset + indices_subtitle_texture_number;
-static constexpr int indices_outline_content_number = 8;
-static constexpr int indices_crop_guess_offset = indices_outline_content_offset + indices_outline_content_number;
-static constexpr int indices_crop_guess_number = 8;
-
-static constexpr unsigned int indices[] = {
- 0, 1, 3, // video texture triangle #1
- 1, 2, 3, // video texture triangle #2
- 4, 5, 7, // subtitle texture triangle #1
- 5, 6, 7, // subtitle texture triangle #2
- 8, 9, // outline content line #1
- 9, 10, // outline content line #2
- 10, 11, // outline content line #3
- 11, 8, // outline content line #4
- 12, 13, // crop guess line #1
- 13, 14, // crop guess line #2
- 14, 15, // crop guess line #3
- 15, 12, // crop guess line #4
-};
-
-/* Offsets of things in the GL_ARRAY_BUFFER */
-static constexpr int array_buffer_video_offset = 0;
-static constexpr int array_buffer_subtitle_offset = array_buffer_video_offset + 4 * 5 * sizeof(float);
-static constexpr int array_buffer_outline_content_offset = array_buffer_subtitle_offset + 4 * 5 * sizeof(float);
-static constexpr int array_buffer_crop_guess_offset = array_buffer_outline_content_offset + 4 * 5 * sizeof(float);
-
-
void
GLVideoView::setup_shaders()
{
@@ -288,8 +260,7 @@ GLVideoView::setup_shaders()
get_information(GL_VERSION);
get_information(GL_SHADING_LANGUAGE_VERSION);
- glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
- check_gl_error("glBufferData");
+ _coords.setup();
auto texture_0 = glGetUniformLocation(_view.program(), "texture_sampler_0");
check_gl_error("glGetUniformLocation");
@@ -362,10 +333,6 @@ GLVideoView::setup_shaders()
check_gl_error("glEnable");
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
check_gl_error("glBlendFunc");
-
- /* Reserve space for the GL_ARRAY_BUFFER */
- glBufferData(GL_ARRAY_BUFFER, 16 * 5 * sizeof(float), nullptr, GL_STATIC_DRAW);
- check_gl_error("glBufferData");
}
@@ -424,21 +391,20 @@ GLVideoView::draw()
for (auto& texture: _video_textures) {
texture->bind();
}
- glDrawElements(GL_TRIANGLES, indices_video_texture_number, GL_UNSIGNED_INT, reinterpret_cast<void*>(indices_video_texture_offset * sizeof(int)));
+ _image.draw();
+
if (_have_subtitle_to_render) {
glUniform1i(_fragment_type, static_cast<GLint>(FragmentType::REC709_SUBTITLE));
_subtitle_texture->bind();
- glDrawElements(GL_TRIANGLES, indices_subtitle_texture_number, GL_UNSIGNED_INT, reinterpret_cast<void*>(indices_subtitle_texture_offset * sizeof(int)));
+ _subtitle.draw();
}
if (_viewer->outline_content()) {
glUniform1i(_fragment_type, static_cast<GLint>(FragmentType::OUTLINE_CONTENT));
- glDrawElements(GL_LINES, indices_outline_content_number, GL_UNSIGNED_INT, reinterpret_cast<void*>(indices_outline_content_offset * sizeof(int)));
- check_gl_error("glDrawElements");
+ _outline_content.draw();
}
if (auto guess = _viewer->crop_guess()) {
glUniform1i(_fragment_type, static_cast<GLint>(FragmentType::CROP_GUESS));
- glDrawElements(GL_LINES, indices_crop_guess_number, GL_UNSIGNED_INT, reinterpret_cast<void*>(indices_crop_guess_offset * sizeof(int)));
- check_gl_error("glDrawElements");
+ _crop_guess.draw();
}
if (pv) {
@@ -509,36 +475,27 @@ GLVideoView::set_image(shared_ptr<const PlayerVideo> pv)
auto const sizing_changed = _last_canvas_size.changed() || _last_inter_position.changed() || _last_inter_size.changed() || _last_out_size.changed();
- using namespace dcpomatic::gl;
-
if (sizing_changed) {
- const auto video = _optimisation == Optimisation::NONE
- ? Rectangle(canvas_size, x_offset, y_offset, out_size)
- : Rectangle(canvas_size, inter_position.x + x_offset, inter_position.y + y_offset, inter_size);
-
- glBufferSubData(GL_ARRAY_BUFFER, array_buffer_video_offset, video.size(), video.vertices());
- check_gl_error("glBufferSubData (video)");
+ if (_optimisation == Optimisation::NONE) {
+ _image.set(canvas_size, x_offset, y_offset, out_size);
+ } else {
+ _image.set(canvas_size, inter_position.x + x_offset, inter_position.y + y_offset, inter_size);
+ }
- const auto outline_content = Rectangle(canvas_size, inter_position.x + x_offset, inter_position.y + y_offset, inter_size);
- glBufferSubData(GL_ARRAY_BUFFER, array_buffer_outline_content_offset, outline_content.size(), outline_content.vertices());
- check_gl_error("glBufferSubData (outline_content)");
+ _outline_content.set(canvas_size, inter_position.x + x_offset, inter_position.y + y_offset, inter_size);
}
if ((sizing_changed || _last_crop_guess.changed()) && crop_guess) {
- auto const crop_guess_rectangle = Rectangle(
+ _crop_guess.set(
canvas_size,
inter_position.x + x_offset + inter_size.width * crop_guess->x,
inter_position.y + y_offset + inter_size.height * crop_guess->y,
- dcp::Size(inter_size.width * crop_guess->width, inter_size.height * crop_guess->height)
+ wxSize(inter_size.width * crop_guess->width, inter_size.height * crop_guess->height)
);
- glBufferSubData(GL_ARRAY_BUFFER, array_buffer_crop_guess_offset, crop_guess_rectangle.size(), crop_guess_rectangle.vertices());
- check_gl_error("glBufferSubData (crop_guess_rectangle)");
}
if (_have_subtitle_to_render) {
- const auto subtitle = Rectangle(canvas_size, inter_position.x + x_offset + text->position.x, inter_position.y + y_offset + text->position.y, text->image->size());
- glBufferSubData(GL_ARRAY_BUFFER, array_buffer_subtitle_offset, subtitle.size(), subtitle.vertices());
- check_gl_error("glBufferSubData (subtitle)");
+ _subtitle.set(canvas_size, inter_position.x + x_offset + text->position.x, inter_position.y + y_offset + text->position.y, text->image->size());
}
_rec2020 = pv->colour_conversion() && pv->colour_conversion()->about_equal(dcp::ColourConversion::rec2020_to_xyz(), 1e-6);
@@ -563,6 +520,10 @@ GLVideoView::set_image(shared_ptr<const PlayerVideo> pv)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
check_gl_error("glTexParameterf");
+
+ if (sizing_changed || _have_subtitle_to_render) {
+ _coords.set_array_buffer();
+ }
}
@@ -618,10 +579,10 @@ void
GLVideoView::create_textures()
{
for (int i = 0; i < 3; ++i) {
- std::unique_ptr<dcpomatic::gl::Texture> texture(new dcpomatic::gl::Texture(_optimisation == Optimisation::JPEG2000 ? 2 : 1, i));
+ std::unique_ptr<Texture> texture(new Texture(_optimisation == Optimisation::JPEG2000 ? 2 : 1, i));
_video_textures.push_back(std::move(texture));
}
- _subtitle_texture.reset(new dcpomatic::gl::Texture(1, 3));
+ _subtitle_texture.reset(new Texture(1, 3));
}
diff --git a/src/wx/gl_video_view.h b/src/wx/gl_video_view.h
index c90429767..707d4a2af 100644
--- a/src/wx/gl_video_view.h
+++ b/src/wx/gl_video_view.h
@@ -115,6 +115,12 @@ private:
std::unique_ptr<dcpomatic::gl::Texture> _subtitle_texture;
bool _have_subtitle_to_render = false;
+ dcpomatic::gl::Coords _coords;
+ dcpomatic::gl::Coords::FilledRectangle _image;
+ dcpomatic::gl::Coords::OutlineRectangle _outline_content;
+ dcpomatic::gl::Coords::OutlineRectangle _crop_guess;
+ dcpomatic::gl::Coords::FilledRectangle _subtitle;
+
GLint _fragment_type;
std::shared_ptr<wxTimer> _timer;