diff options
| author | Carl Hetherington <cth@carlh.net> | 2025-04-27 21:49:07 +0200 |
|---|---|---|
| committer | Carl Hetherington <cth@carlh.net> | 2025-04-28 02:31:15 +0200 |
| commit | d35e71e05c79d5343b88c887263bd4df190ad674 (patch) | |
| tree | d21482ee035750a6cf6ad2232797782a427c22a5 | |
| parent | 789b3ba91a4444a73090a2c3a1b44cc97b488a33 (diff) | |
Add new Coords class to manage OpenGL coordinates and use it for the video view.
| -rw-r--r-- | src/wx/gl_util.cc | 197 | ||||
| -rw-r--r-- | src/wx/gl_util.h | 131 | ||||
| -rw-r--r-- | src/wx/gl_video_view.cc | 89 | ||||
| -rw-r--r-- | src/wx/gl_video_view.h | 6 |
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; |
