From 048e179572aada0e70376fb46a50e3ad486c8025 Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Fri, 10 Nov 2023 18:00:18 +0100 Subject: [PATCH] LibAccelGfx: Use wrapping functions with error check for OpenGL calls This change introduces GL.h with error check wrappers for all the OpenGL functions we used so far. For now, the error check is simply: `VERIFY(glGetError() == GL_NO_ERROR);` but that is better than continuing execution after encounting an error. --- Userland/Libraries/LibAccelGfx/CMakeLists.txt | 1 + Userland/Libraries/LibAccelGfx/Canvas.cpp | 9 +- Userland/Libraries/LibAccelGfx/GL.cpp | 176 ++++++++++++++++++ Userland/Libraries/LibAccelGfx/GL.h | 75 ++++++++ Userland/Libraries/LibAccelGfx/Painter.cpp | 110 ++++------- Userland/Libraries/LibAccelGfx/Painter.h | 3 +- Userland/Libraries/LibAccelGfx/Program.cpp | 57 +----- Userland/Libraries/LibAccelGfx/Program.h | 12 +- 8 files changed, 313 insertions(+), 130 deletions(-) create mode 100644 Userland/Libraries/LibAccelGfx/GL.cpp create mode 100644 Userland/Libraries/LibAccelGfx/GL.h diff --git a/Userland/Libraries/LibAccelGfx/CMakeLists.txt b/Userland/Libraries/LibAccelGfx/CMakeLists.txt index 74744c2ce9..94ec615d6b 100644 --- a/Userland/Libraries/LibAccelGfx/CMakeLists.txt +++ b/Userland/Libraries/LibAccelGfx/CMakeLists.txt @@ -2,6 +2,7 @@ include(accelerated_graphics) if (HAS_ACCELERATED_GRAPHICS) set(SOURCES + GL.cpp Canvas.cpp Context.cpp Painter.cpp diff --git a/Userland/Libraries/LibAccelGfx/Canvas.cpp b/Userland/Libraries/LibAccelGfx/Canvas.cpp index 9bfddc1b1e..622f88a06a 100644 --- a/Userland/Libraries/LibAccelGfx/Canvas.cpp +++ b/Userland/Libraries/LibAccelGfx/Canvas.cpp @@ -4,8 +4,8 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include "Canvas.h" -#include +#include +#include #include namespace AccelGfx { @@ -28,13 +28,12 @@ void Canvas::initialize() { m_surface = m_context.create_surface(width(), height()); m_context.set_active_surface(m_surface); - glViewport(0, 0, width(), height()); + GL::set_viewport({ 0, 0, width(), height() }); } void Canvas::flush() { - glPixelStorei(GL_PACK_ALIGNMENT, 1); - glReadPixels(0, 0, width(), height(), GL_BGRA, GL_UNSIGNED_BYTE, m_bitmap->scanline(0)); + GL::read_pixels({ 0, 0, width(), height() }, *m_bitmap); } Canvas::~Canvas() diff --git a/Userland/Libraries/LibAccelGfx/GL.cpp b/Userland/Libraries/LibAccelGfx/GL.cpp new file mode 100644 index 0000000000..9dad447bd2 --- /dev/null +++ b/Userland/Libraries/LibAccelGfx/GL.cpp @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2023, Aliaksandr Kalenik + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#define GL_GLEXT_PROTOTYPES + +#include +#include +#include + +namespace AccelGfx::GL { + +static void verify_no_error() +{ + VERIFY(glGetError() == GL_NO_ERROR); +} + +void set_viewport(Gfx::IntRect rect) +{ + glViewport(rect.left(), rect.top(), rect.width(), rect.height()); + verify_no_error(); +} + +void enable_blending() +{ + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + verify_no_error(); +} + +void read_pixels(Gfx::IntRect rect, Gfx::Bitmap& bitmap) +{ + VERIFY(bitmap.format() == Gfx::BitmapFormat::BGRA8888); + glPixelStorei(GL_PACK_ALIGNMENT, 1); + glReadPixels(rect.left(), rect.top(), rect.width(), rect.height(), GL_BGRA, GL_UNSIGNED_BYTE, bitmap.scanline(0)); + verify_no_error(); +} + +Shader create_shader(ShaderType type, char const* source) +{ + GLuint shader = glCreateShader(type == ShaderType::Vertex ? GL_VERTEX_SHADER : GL_FRAGMENT_SHADER); + glShaderSource(shader, 1, &source, nullptr); + glCompileShader(shader); + + int success; + glGetShaderiv(shader, GL_COMPILE_STATUS, &success); + if (!success) { + char buffer[512]; + glGetShaderInfoLog(shader, sizeof(buffer), nullptr, buffer); + dbgln("GLSL shader compilation failed: {}", buffer); + VERIFY_NOT_REACHED(); + } + + verify_no_error(); + + return { shader }; +} + +Program create_program(Shader const& vertex_shader, Shader const& fragment_shader) +{ + GLuint program = glCreateProgram(); + + glAttachShader(program, vertex_shader.id); + glAttachShader(program, fragment_shader.id); + glLinkProgram(program); + + int linked; + glGetProgramiv(program, GL_LINK_STATUS, &linked); + if (!linked) { + char buffer[512]; + glGetProgramInfoLog(program, sizeof(buffer), nullptr, buffer); + dbgln("GLSL program linking failed: {}", buffer); + VERIFY_NOT_REACHED(); + } + + glDeleteShader(vertex_shader.id); + glDeleteShader(fragment_shader.id); + + verify_no_error(); + + return { program }; +} + +void use_program(Program const& program) +{ + glUseProgram(program.id); + verify_no_error(); +} + +VertexAttribute get_attribute_location(Program const& program, char const* name) +{ + auto id = glGetAttribLocation(program.id, name); + verify_no_error(); + return { id }; +} + +Uniform get_uniform_location(Program const& program, char const* name) +{ + auto id = glGetUniformLocation(program.id, name); + verify_no_error(); + return { id }; +} + +void delete_program(Program const& program) +{ + glDeleteProgram(program.id); + verify_no_error(); +} + +Texture create_texture() +{ + GLuint texture; + glGenTextures(1, &texture); + verify_no_error(); + return { texture }; +} + +void bind_texture(Texture const& texture) +{ + glBindTexture(GL_TEXTURE_2D, texture.id); + verify_no_error(); +} + +void upload_texture_data(Texture const& texture, Gfx::Bitmap const& bitmap) +{ + VERIFY(bitmap.format() == Gfx::BitmapFormat::BGRx8888 || bitmap.format() == Gfx::BitmapFormat::BGRA8888); + bind_texture(texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bitmap.width(), bitmap.height(), 0, GL_BGRA, GL_UNSIGNED_BYTE, bitmap.scanline(0)); + verify_no_error(); +} + +void delete_texture(Texture const& texture) +{ + glDeleteTextures(1, &texture.id); + verify_no_error(); +} + +void set_uniform(Uniform const& uniform, float value1, float value2, float value3, float value4) +{ + glUniform4f(uniform.id, value1, value2, value3, value4); + verify_no_error(); +} + +void set_vertex_attribute(VertexAttribute const& attribute, Span values, int number_of_components) +{ + glVertexAttribPointer(attribute.id, number_of_components, GL_FLOAT, GL_FALSE, number_of_components * sizeof(float), values.data()); + glEnableVertexAttribArray(attribute.id); + verify_no_error(); +} + +void set_texture_scale_mode(ScalingMode scaling_mode) +{ + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, scaling_mode == ScalingMode::Nearest ? GL_NEAREST : GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, scaling_mode == ScalingMode::Nearest ? GL_NEAREST : GL_LINEAR); + verify_no_error(); +} + +void clear_color(Gfx::Color const& color) +{ + glClearColor(color.red() / 255.0f, color.green() / 255.0f, color.blue() / 255.0f, color.alpha() / 255.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + verify_no_error(); +} + +void draw_arrays(DrawPrimitive draw_primitive, size_t count) +{ + GLenum mode = GL_TRIANGLES; + if (draw_primitive == DrawPrimitive::TriangleFan) + mode = GL_TRIANGLE_FAN; + glDrawArrays(mode, 0, count); + verify_no_error(); +} + +} diff --git a/Userland/Libraries/LibAccelGfx/GL.h b/Userland/Libraries/LibAccelGfx/GL.h new file mode 100644 index 0000000000..a69dba0c4f --- /dev/null +++ b/Userland/Libraries/LibAccelGfx/GL.h @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2023, Aliaksandr Kalenik + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +namespace AccelGfx::GL { + +enum class ShaderType { + Vertex, + Fragment, +}; + +struct Shader { + GLuint id; +}; + +struct Program { + GLuint id; +}; + +struct VertexAttribute { + GLint id; +}; + +struct Uniform { + GLint id; +}; + +struct Texture { + GLuint id; +}; + +void set_viewport(Gfx::IntRect); +void enable_blending(); + +void read_pixels(Gfx::IntRect, Gfx::Bitmap&); + +Shader create_shader(ShaderType type, char const* source); +Program create_program(Shader const& vertex_shader, Shader const& fragment_shader); +void use_program(Program const&); +VertexAttribute get_attribute_location(Program const&, char const* name); +Uniform get_uniform_location(Program const&, char const* name); +void delete_program(Program const&); + +Texture create_texture(); +void bind_texture(Texture const&); +void upload_texture_data(Texture const& texture, Gfx::Bitmap const& bitmap); +void delete_texture(Texture const&); + +void set_uniform(Uniform const& uniform, float, float, float, float); +void set_vertex_attribute(VertexAttribute const& attribute, Span values, int number_of_components); + +enum class ScalingMode { + Nearest, + Linear, +}; +void set_texture_scale_mode(ScalingMode); + +void clear_color(Gfx::Color const&); + +enum class DrawPrimitive { + Triangles, + TriangleFan, +}; + +void draw_arrays(DrawPrimitive, size_t count); + +} diff --git a/Userland/Libraries/LibAccelGfx/Painter.cpp b/Userland/Libraries/LibAccelGfx/Painter.cpp index 1522f04abb..82f310be65 100644 --- a/Userland/Libraries/LibAccelGfx/Painter.cpp +++ b/Userland/Libraries/LibAccelGfx/Painter.cpp @@ -5,13 +5,10 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#define GL_GLEXT_PROTOTYPES - -#include "Painter.h" -#include "Canvas.h" #include -#include -#include +#include +#include +#include #include #include @@ -89,10 +86,9 @@ Painter::Painter(Context& context) : m_context(context) , m_rectangle_program(Program::create(vertex_shader_source, solid_color_fragment_shader_source)) , m_blit_program(Program::create(blit_vertex_shader_source, blit_fragment_shader_source)) + , m_glyphs_texture(GL::create_texture()) { m_state_stack.empend(State()); - - glGenTextures(1, &m_glyphs_texture); } Painter::~Painter() @@ -102,9 +98,7 @@ Painter::~Painter() void Painter::clear(Gfx::Color color) { - auto [red, green, blue, alpha] = gfx_color_to_opengl_color(color); - glClearColor(red, green, blue, alpha); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + GL::clear_color(color); } void Painter::fill_rect(Gfx::IntRect rect, Gfx::Color color) @@ -136,18 +130,13 @@ void Painter::fill_rect(Gfx::FloatRect rect, Gfx::Color color) m_rectangle_program.use(); - GLuint position_attribute = m_rectangle_program.get_attribute_location("aVertexPosition"); - GLuint color_uniform = m_rectangle_program.get_uniform_location("uColor"); + auto position_attribute = m_rectangle_program.get_attribute_location("aVertexPosition"); + auto color_uniform = m_rectangle_program.get_uniform_location("uColor"); - glUniform4f(color_uniform, red, green, blue, alpha); - - glVertexAttribPointer(position_attribute, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), vertices.data()); - glEnableVertexAttribArray(position_attribute); - - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + GL::set_uniform(color_uniform, red, green, blue, alpha); + GL::set_vertex_attribute(position_attribute, vertices, 2); + GL::enable_blending(); + GL::draw_arrays(GL::DrawPrimitive::TriangleFan, 4); } void Painter::draw_line(Gfx::IntPoint a, Gfx::IntPoint b, float thickness, Gfx::Color color) @@ -172,18 +161,13 @@ void Painter::draw_line(Gfx::FloatPoint a, Gfx::FloatPoint b, float thickness, C m_rectangle_program.use(); - GLuint position_attribute = m_rectangle_program.get_attribute_location("aVertexPosition"); - GLuint color_uniform = m_rectangle_program.get_uniform_location("uColor"); + auto position_attribute = m_rectangle_program.get_attribute_location("aVertexPosition"); + auto color_uniform = m_rectangle_program.get_uniform_location("uColor"); - glUniform4f(color_uniform, red, green, blue, alpha); - - glVertexAttribPointer(position_attribute, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), vertices.data()); - glEnableVertexAttribArray(position_attribute); - - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + GL::set_uniform(color_uniform, red, green, blue, alpha); + GL::set_vertex_attribute(position_attribute, vertices, 2); + GL::enable_blending(); + GL::draw_arrays(GL::DrawPrimitive::TriangleFan, 4); } void Painter::draw_scaled_bitmap(Gfx::IntRect const& dest_rect, Gfx::Bitmap const& bitmap, Gfx::IntRect const& src_rect, ScalingMode scaling_mode) @@ -201,13 +185,13 @@ static Gfx::FloatRect to_texture_space(Gfx::FloatRect rect, Gfx::IntSize image_s return { x, y, width, height }; } -static GLenum to_gl_scaling_mode(Painter::ScalingMode scaling_mode) +static GL::ScalingMode to_gl_scaling_mode(Painter::ScalingMode scaling_mode) { switch (scaling_mode) { case Painter::ScalingMode::NearestNeighbor: - return GL_NEAREST; + return GL::ScalingMode::Nearest; case Painter::ScalingMode::Bilinear: - return GL_LINEAR; + return GL::ScalingMode::Linear; default: VERIFY_NOT_REACHED(); } @@ -217,19 +201,12 @@ void Painter::draw_scaled_bitmap(Gfx::FloatRect const& dst_rect, Gfx::Bitmap con { m_blit_program.use(); - GLuint texture; - // FIXME: We should reuse textures across repaints if possible. - glGenTextures(1, &texture); - glBindTexture(GL_TEXTURE_2D, texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_BGRA, bitmap.width(), bitmap.height(), 0, GL_BGRA, GL_UNSIGNED_BYTE, bitmap.scanline(0)); + auto texture = GL::create_texture(); + GL::upload_texture_data(texture, bitmap); - GLenum scaling_mode_gl = to_gl_scaling_mode(scaling_mode); - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, scaling_mode_gl); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, scaling_mode_gl); + auto scaling_mode_gl = to_gl_scaling_mode(scaling_mode); + GL::set_texture_scale_mode(scaling_mode_gl); auto dst_rect_in_clip_space = to_clip_space(transform().map(dst_rect)); auto src_rect_in_texture_space = to_texture_space(src_rect, bitmap.size()); @@ -249,18 +226,16 @@ void Painter::draw_scaled_bitmap(Gfx::FloatRect const& dst_rect, Gfx::Bitmap con add_vertex(dst_rect_in_clip_space.bottom_right(), src_rect_in_texture_space.bottom_right()); add_vertex(dst_rect_in_clip_space.top_right(), src_rect_in_texture_space.top_right()); - GLuint vertex_position_attribute = m_blit_program.get_attribute_location("aVertexPosition"); - glVertexAttribPointer(vertex_position_attribute, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(float), vertices.data()); - glEnableVertexAttribArray(vertex_position_attribute); + auto vertex_position_attribute = m_blit_program.get_attribute_location("aVertexPosition"); + GL::set_vertex_attribute(vertex_position_attribute, vertices, 4); - GLuint color_uniform = m_blit_program.get_uniform_location("uColor"); - glUniform4f(color_uniform, 1, 1, 1, 1); + auto color_uniform = m_blit_program.get_uniform_location("uColor"); + GL::set_uniform(color_uniform, 1, 1, 1, 1); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + GL::enable_blending(); + GL::draw_arrays(GL::DrawPrimitive::TriangleFan, 4); - glDeleteTextures(1, &texture); + GL::delete_texture(texture); } void Painter::prepare_glyph_texture(HashMap> const& unique_glyphs) @@ -314,8 +289,7 @@ void Painter::prepare_glyph_texture(HashMap> co m_glyphs_texture_size = glyphs_texture_bitmap->size(); - glBindTexture(GL_TEXTURE_2D, m_glyphs_texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_BGRA, glyphs_texture_bitmap->width(), glyphs_texture_bitmap->height(), 0, GL_BGRA, GL_UNSIGNED_BYTE, glyphs_texture_bitmap->scanline(0)); + GL::upload_texture_data(m_glyphs_texture, *glyphs_texture_bitmap); } void Painter::draw_glyph_run(Vector const& glyph_run, Color const& color) @@ -379,21 +353,15 @@ void Painter::draw_glyph_run(Vector const& glyph_run, Col m_blit_program.use(); - glBindTexture(GL_TEXTURE_2D, m_glyphs_texture); + GL::bind_texture(m_glyphs_texture); + GL::set_texture_scale_mode(GL::ScalingMode::Nearest); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + auto position_attribute = m_blit_program.get_attribute_location("aVertexPosition"); + auto color_uniform = m_blit_program.get_uniform_location("uColor"); - GLuint position_attribute = m_blit_program.get_attribute_location("aVertexPosition"); - GLuint color_uniform = m_blit_program.get_uniform_location("uColor"); - - glUniform4f(color_uniform, red, green, blue, alpha); - - glVertexAttribPointer(position_attribute, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(float), vertices.data()); - glEnableVertexAttribArray(position_attribute); - - glDrawArrays(GL_TRIANGLES, 0, vertices.size() / 4); + GL::set_uniform(color_uniform, red, green, blue, alpha); + GL::set_vertex_attribute(position_attribute, vertices, 4); + GL::draw_arrays(GL::DrawPrimitive::Triangles, vertices.size() / 4); } void Painter::save() diff --git a/Userland/Libraries/LibAccelGfx/Painter.h b/Userland/Libraries/LibAccelGfx/Painter.h index 686e9c538d..e6b059936c 100644 --- a/Userland/Libraries/LibAccelGfx/Painter.h +++ b/Userland/Libraries/LibAccelGfx/Painter.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -89,7 +90,7 @@ private: HashMap m_glyphs_texture_map; Gfx::IntSize m_glyphs_texture_size; - GLuint m_glyphs_texture; + GL::Texture m_glyphs_texture; }; } diff --git a/Userland/Libraries/LibAccelGfx/Program.cpp b/Userland/Libraries/LibAccelGfx/Program.cpp index dab717f157..111fcf6f17 100644 --- a/Userland/Libraries/LibAccelGfx/Program.cpp +++ b/Userland/Libraries/LibAccelGfx/Program.cpp @@ -4,78 +4,41 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#define GL_GLEXT_PROTOTYPES - #include #include -#include -#include +#include #include namespace AccelGfx { -static GLuint create_shader(GLenum type, char const* source) -{ - GLuint shader = glCreateShader(type); - glShaderSource(shader, 1, &source, nullptr); - glCompileShader(shader); - - int success; - glGetShaderiv(shader, GL_COMPILE_STATUS, &success); - if (!success) { - char buffer[512]; - glGetShaderInfoLog(shader, sizeof(buffer), nullptr, buffer); - dbgln("GLSL shader compilation failed: {}", buffer); - VERIFY_NOT_REACHED(); - } - - return shader; -} - Program Program::create(char const* vertex_shader_source, char const* fragment_shader_source) { - GLuint program = glCreateProgram(); + auto vertex_shader = GL::create_shader(GL::ShaderType::Vertex, vertex_shader_source); + auto fragment_shader = GL::create_shader(GL::ShaderType::Fragment, fragment_shader_source); - auto vertex_shader = create_shader(GL_VERTEX_SHADER, vertex_shader_source); - auto fragment_shader = create_shader(GL_FRAGMENT_SHADER, fragment_shader_source); - - glAttachShader(program, vertex_shader); - glAttachShader(program, fragment_shader); - glLinkProgram(program); - - int linked; - glGetProgramiv(program, GL_LINK_STATUS, &linked); - if (!linked) { - char buffer[512]; - glGetProgramInfoLog(program, sizeof(buffer), nullptr, buffer); - dbgln("GLSL program linking failed: {}", buffer); - VERIFY_NOT_REACHED(); - } - - glDeleteShader(vertex_shader); - glDeleteShader(fragment_shader); + auto program = GL::create_program(vertex_shader, fragment_shader); return Program { program }; } void Program::use() { - glUseProgram(m_id); + GL::use_program(m_program); } -GLuint Program::get_attribute_location(char const* name) +GL::VertexAttribute Program::get_attribute_location(char const* name) { - return glGetAttribLocation(m_id, name); + return GL::get_attribute_location(m_program, name); } -GLuint Program::get_uniform_location(char const* name) +GL::Uniform Program::get_uniform_location(char const* name) { - return glGetUniformLocation(m_id, name); + return GL::get_uniform_location(m_program, name); } Program::~Program() { - glDeleteProgram(m_id); + GL::delete_program(m_program); } } diff --git a/Userland/Libraries/LibAccelGfx/Program.h b/Userland/Libraries/LibAccelGfx/Program.h index 2de71da82d..2a5a62dd84 100644 --- a/Userland/Libraries/LibAccelGfx/Program.h +++ b/Userland/Libraries/LibAccelGfx/Program.h @@ -7,7 +7,7 @@ #pragma once #include -#include +#include namespace AccelGfx { @@ -18,18 +18,18 @@ public: static Program create(char const* vertex_shader_source, char const* fragment_shader_source); void use(); - GLuint get_attribute_location(char const* name); - GLuint get_uniform_location(char const* name); + GL::VertexAttribute get_attribute_location(char const* name); + GL::Uniform get_uniform_location(char const* name); ~Program(); private: - Program(GLuint id) - : m_id(id) + Program(GL::Program program) + : m_program(program) { } - GLuint m_id { 0 }; + GL::Program m_program; }; }