diff --git a/CMakeLists.txt b/CMakeLists.txt index 1f852c981d073fe4d3e5343de426cbb5140ba922..c9d4d37dd9febb89a4ffca6a93a270887d823fdb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,6 +63,7 @@ option(SRB2_CONFIG_ENABLE_WEBM_MOVIES "Enable WebM recording support" ON) option(SRB2_CONFIG_ENABLE_DISCORDRPC "Enable Discord RPC features" ON) option(SRB2_CONFIG_HWRENDER "Enable hardware render (OpenGL) support" ON) option(SRB2_CONFIG_STATIC_OPENGL "Enable static linking GL (do not do this)" OFF) +option(SRB2_CONFIG_GLES2 "Enable GLES2 based software support" OFF) option(SRB2_CONFIG_ERRORMODE "Compile C code with warnings treated as errors." OFF) option(SRB2_CONFIG_DEBUGMODE "Compile with PARANOIA, ZDEBUG, RANGECHECK and PACKETDROP defined." OFF) option(SRB2_CONFIG_DEV_BUILD "Compile a development build." OFF) diff --git a/src/rhi/CMakeLists.txt b/src/rhi/CMakeLists.txt index 51407bb89526cc90197d4e66a7cd2a70a9c18ece..d5f975c3fc77780340bd8979e8667c52dbc0a0ff 100644 --- a/src/rhi/CMakeLists.txt +++ b/src/rhi/CMakeLists.txt @@ -6,4 +6,12 @@ target_sources(SRB2SDL2 PRIVATE shader_load_context.hpp ) -add_subdirectory(gl2) +if("${SRB2_CONFIG_GLES2}") + option(SRB2_CONFIG_GLES3 "Enable GLES3 extentions in the GLES backend" OFF) + if("${SRB2_CONFIG_GLES3}") + target_compile_definitions(SRB2SDL2 PRIVATE -DHAVE_GLES3) + endif() + add_subdirectory(gles2) +else() + add_subdirectory(gl2) +endif() diff --git a/src/rhi/gles2/CMakeLists.txt b/src/rhi/gles2/CMakeLists.txt index eaa80c4fa82673033cb8648b4e4f74f6c43060f2..2db33119f40f20f7ec1af8f175614b8335bab8cb 100644 --- a/src/rhi/gles2/CMakeLists.txt +++ b/src/rhi/gles2/CMakeLists.txt @@ -1 +1,4 @@ -# Backend not available yet :) +target_sources(SRB2SDL2 PRIVATE + gles2_rhi.cpp + gles2_rhi.hpp +) diff --git a/src/rhi/gles2/gles2_rhi.cpp b/src/rhi/gles2/gles2_rhi.cpp index 3bdfff0f5f786ec33e7b8d771ee6762d275b7a43..6460cad149cfd480a070a68585b4e87331656bb0 100644 --- a/src/rhi/gles2/gles2_rhi.cpp +++ b/src/rhi/gles2/gles2_rhi.cpp @@ -12,33 +12,51 @@ #include <memory> #include <string> +#include <string_view> #include <tuple> #include <unordered_map> #include <utility> +#include <fmt/format.h> #include <glad/gles2.h> +#include <glm/gtc/type_ptr.hpp> -using namespace srb2; +#include "../shader_load_context.hpp" -using srb2::rhi::Gles2Platform; -using srb2::rhi::Gles2Rhi; +using namespace srb2; +using namespace rhi; +#ifndef NDEBUG +#define GL_ASSERT \ + while (1) \ + { \ + GLenum __err = gl_->GetError(); \ + if (__err != GL_NO_ERROR) \ + { \ + I_Error("GL Error at %s %d: 0x%x", __FILE__, __LINE__, __err); \ + } \ + else \ + { \ + break; \ + } \ + } +#else +#define GL_ASSERT ; +#endif namespace { -template <typename D, typename B> -std::unique_ptr<D, std::default_delete<D>> static_unique_ptr_cast(std::unique_ptr<B, std::default_delete<B>> ptr) -{ - D* derived = static_cast<D*>(ptr.release()); - return std::unique_ptr<D, std::default_delete<D>>(derived, std::default_delete<D>()); -} - constexpr GLenum map_pixel_format(rhi::PixelFormat format) { switch (format) { + case rhi::PixelFormat::kR8: + return GL_LUMINANCE; + case rhi::PixelFormat::kRG8: + return GL_LUMINANCE_ALPHA; + case rhi::PixelFormat::kRGB8: + return GL_RGB8_OES; case rhi::PixelFormat::kRGBA8: - // requires extension GL_OES_rgb8_rgba8, which is always requested return GL_RGBA8_OES; case rhi::PixelFormat::kDepth16: return GL_DEPTH_COMPONENT16; @@ -61,6 +79,16 @@ constexpr std::tuple<GLenum, GLenum, GLuint> map_pixel_data_format(rhi::PixelFor type = GL_UNSIGNED_BYTE; size = 1; break; + case rhi::PixelFormat::kRG8: + layout = GL_LUMINANCE_ALPHA; + type = GL_UNSIGNED_BYTE; + size = 2; + break; + case rhi::PixelFormat::kRGB8: + layout = GL_RGB; + type = GL_UNSIGNED_BYTE; + size = 3; + break; case rhi::PixelFormat::kRGBA8: layout = GL_RGBA; type = GL_UNSIGNED_BYTE; @@ -82,6 +110,53 @@ constexpr GLenum map_texture_format(rhi::TextureFormat format) return GL_RGB; case rhi::TextureFormat::kLuminance: return GL_LUMINANCE; + case rhi::TextureFormat::kLuminanceAlpha: + return GL_LUMINANCE_ALPHA; + default: + return GL_ZERO; + } +} + +constexpr GLenum map_texture_wrap(rhi::TextureWrapMode wrap) +{ + switch (wrap) + { + case rhi::TextureWrapMode::kClamp: + return GL_CLAMP_TO_EDGE; + case rhi::TextureWrapMode::kRepeat: + return GL_REPEAT; + case rhi::TextureWrapMode::kMirroredRepeat: + return GL_MIRRORED_REPEAT; + default: + return GL_REPEAT; + } +} + +constexpr GLenum map_texture_filter(rhi::TextureFilterMode filter) +{ + switch (filter) + { + case rhi::TextureFilterMode::kNearest: + return GL_NEAREST; + case rhi::TextureFilterMode::kLinear: + return GL_LINEAR; + default: + return GL_NEAREST; + } +} + +constexpr GLenum map_internal_texture_format(rhi::TextureFormat format) +{ + switch (format) + { + case rhi::TextureFormat::kRGBA: + return GL_RGBA8_OES; + case rhi::TextureFormat::kRGB: + return GL_RGB8_OES; + case rhi::TextureFormat::kLuminance: + return GL_LUMINANCE; + case rhi::TextureFormat::kLuminanceAlpha: + return GL_LUMINANCE_ALPHA; default: return GL_ZERO; } @@ -260,9 +335,10 @@ constexpr const char* map_vertex_attribute_symbol_name(rhi::VertexAttributeName } } -/* -constexpr const char* map_vertex_attribute_enable_define(rhi::VertexAttributeName name) { - switch (name) { +constexpr const char* map_vertex_attribute_enable_define(rhi::VertexAttributeName name) +{ + switch (name) + { case rhi::VertexAttributeName::kPosition: return "ENABLE_VA_POSITION"; case rhi::VertexAttributeName::kNormal: @@ -277,7 +353,6 @@ constexpr const char* map_vertex_attribute_enable_define(rhi::VertexAttributeNam return nullptr; } } -*/ constexpr const char* map_uniform_attribute_symbol_name(rhi::UniformName name) { @@ -289,25 +364,97 @@ constexpr const char* map_uniform_attribute_symbol_name(rhi::UniformName name) return "u_modelview"; case rhi::UniformName::kProjection: return "u_projection"; + case rhi::UniformName::kTexCoord0Transform: + return "u_texcoord0_transform"; + case rhi::UniformName::kTexCoord0Min: + return "u_texcoord0_min"; + case rhi::UniformName::kTexCoord0Max: + return "u_texcoord0_max"; + case rhi::UniformName::kTexCoord1Transform: + return "u_texcoord1_transform"; + case rhi::UniformName::kTexCoord1Min: + return "u_texcoord1_min"; + case rhi::UniformName::kTexCoord1Max: + return "u_texcoord1_max"; + case rhi::UniformName::kSampler0IsIndexedAlpha: + return "u_sampler0_is_indexed_alpha"; + case rhi::UniformName::kSampler1IsIndexedAlpha: + return "u_sampler1_is_indexed_alpha"; + case rhi::UniformName::kSampler2IsIndexedAlpha: + return "u_sampler2_is_indexed_alpha"; + case rhi::UniformName::kSampler3IsIndexedAlpha: + return "u_sampler3_is_indexed_alpha"; + case rhi::UniformName::kSampler0Size: + return "u_sampler0_size"; + case rhi::UniformName::kSampler1Size: + return "u_sampler1_size"; + case rhi::UniformName::kSampler2Size: + return "u_sampler2_size"; + case rhi::UniformName::kSampler3Size: + return "u_sampler3_size"; + case rhi::UniformName::kWipeColorizeMode: + return "u_wipe_colorize_mode"; + case rhi::UniformName::kWipeEncoreSwizzle: + return "u_wipe_encore_swizzle"; + case rhi::UniformName::kPostimgWater: + return "u_postimg_water"; + case rhi::UniformName::kPostimgHeat: + return "u_postimg_heat"; default: return nullptr; } } -/* -constexpr const char* map_uniform_attribute_enable_define(rhi::UniformName name) { - switch (name) { +constexpr const char* map_uniform_enable_define(rhi::UniformName name) +{ + switch (name) + { case rhi::UniformName::kTime: return "ENABLE_U_TIME"; - case rhi::UniformName::kModelView: - return "ENABLE_U_MODELVIEW"; case rhi::UniformName::kProjection: return "ENABLE_U_PROJECTION"; + case rhi::UniformName::kModelView: + return "ENABLE_U_MODELVIEW"; + case rhi::UniformName::kTexCoord0Transform: + return "ENABLE_U_TEXCOORD0_TRANSFORM"; + case rhi::UniformName::kTexCoord0Min: + return "ENABLE_U_TEXCOORD0_MIN"; + case rhi::UniformName::kTexCoord0Max: + return "ENABLE_U_TEXCOORD0_MAX"; + case rhi::UniformName::kTexCoord1Transform: + return "ENABLE_U_TEXCOORD1_TRANSFORM"; + case rhi::UniformName::kTexCoord1Min: + return "ENABLE_U_TEXCOORD1_MIN"; + case rhi::UniformName::kTexCoord1Max: + return "ENABLE_U_TEXCOORD1_MAX"; + case rhi::UniformName::kSampler0IsIndexedAlpha: + return "ENABLE_U_SAMPLER0_IS_INDEXED_ALPHA"; + case rhi::UniformName::kSampler1IsIndexedAlpha: + return "ENABLE_U_SAMPLER1_IS_INDEXED_ALPHA"; + case rhi::UniformName::kSampler2IsIndexedAlpha: + return "ENABLE_U_SAMPLER2_IS_INDEXED_ALPHA"; + case rhi::UniformName::kSampler3IsIndexedAlpha: + return "ENABLE_U_SAMPLER3_IS_INDEXED_ALPHA"; + case rhi::UniformName::kSampler0Size: + return "ENABLE_U_SAMPLER0_SIZE"; + case rhi::UniformName::kSampler1Size: + return "ENABLE_U_SAMPLER1_SIZE"; + case rhi::UniformName::kSampler2Size: + return "ENABLE_U_SAMPLER2_SIZE"; + case rhi::UniformName::kSampler3Size: + return "ENABLE_U_SAMPLER3_SIZE"; + case rhi::UniformName::kWipeColorizeMode: + return "ENABLE_U_WIPE_COLORIZE_MODE"; + case rhi::UniformName::kWipeEncoreSwizzle: + return "ENABLE_U_WIPE_ENCORE_SWIZZLE"; + case rhi::UniformName::kPostimgWater: + return "ENABLE_U_POSTIMG_WATER"; + case rhi::UniformName::kPostimgHeat: + return "ENABLE_U_POSTIMG_HEAT"; default: return nullptr; } } -*/ constexpr const char* map_sampler_symbol_name(rhi::SamplerName name) { @@ -326,6 +473,23 @@ constexpr const char* map_sampler_symbol_name(rhi::SamplerName name) } } +constexpr const char* map_sampler_enable_define(rhi::SamplerName name) +{ + switch (name) + { + case rhi::SamplerName::kSampler0: + return "ENABLE_S_SAMPLER0"; + case rhi::SamplerName::kSampler1: + return "ENABLE_S_SAMPLER1"; + case rhi::SamplerName::kSampler2: + return "ENABLE_S_SAMPLER2"; + case rhi::SamplerName::kSampler3: + return "ENABLE_S_SAMPLER3"; + default: + return nullptr; + } +} + constexpr GLenum map_vertex_attribute_format(rhi::VertexAttributeFormat format) { switch (format) @@ -408,151 +572,89 @@ constexpr GLenum map_uniform_format(rhi::UniformFormat format) } } -struct Gles2Texture : public rhi::Texture -{ - GLuint texture; - rhi::TextureDesc desc; - Gles2Texture(GLuint texture, const rhi::TextureDesc& desc) noexcept : texture(texture), desc(desc) {} -}; - -struct Gles2Buffer : public rhi::Buffer -{ - GLuint buffer; - rhi::BufferDesc desc; - Gles2Buffer(GLuint buffer, const rhi::BufferDesc& desc) noexcept : buffer(buffer), desc(desc) {} -}; - -struct Gles2RenderPass : public rhi::RenderPass -{ - rhi::RenderPassDesc desc; - explicit Gles2RenderPass(const rhi::RenderPassDesc& desc) noexcept : desc(desc) {} -}; - -struct Gles2Renderbuffer : public rhi::Renderbuffer -{ - GLuint renderbuffer; - - explicit Gles2Renderbuffer(GLuint renderbuffer) noexcept : renderbuffer(renderbuffer) {} -}; - -struct Gles2Pipeline : public rhi::Pipeline -{ - GLuint vertex_shader = 0; - GLuint fragment_shader = 0; - GLuint program = 0; - std::unordered_map<rhi::VertexAttributeName, GLuint> attrib_locations {2}; - std::unordered_map<rhi::UniformName, GLuint> uniform_locations {2}; - std::unordered_map<rhi::SamplerName, GLuint> sampler_locations {2}; - rhi::PipelineDesc desc; - - Gles2Pipeline() = default; - explicit Gles2Pipeline( - GLuint vertex_shader, - GLuint fragment_shader, - GLuint program, - const rhi::PipelineDesc& desc - ) noexcept - : vertex_shader(vertex_shader), fragment_shader(fragment_shader), program(program), desc(desc) - { - } -}; - -struct Gles2GraphicsContext : public rhi::GraphicsContext -{ -}; - -struct Gles2ActiveUniform -{ - GLenum type; - GLuint location; -}; - } // namespace -Gles2Platform::~Gles2Platform() = default; +Gl2Platform::~Gl2Platform() = default; -Gles2Rhi::Gles2Rhi(std::unique_ptr<Gles2Platform>&& platform) : platform_(std::move(platform)) +Gl2Rhi::Gl2Rhi(std::unique_ptr<Gl2Platform>&& platform, GlLoadFunc load_func) : platform_(std::move(platform)) { + gl_ = std::make_unique<GladGLES2Context>(); + gladLoadGLES2Context(gl_.get(), load_func); } -Gles2Rhi::~Gles2Rhi() = default; +Gl2Rhi::~Gl2Rhi() = default; -rhi::Handle<rhi::RenderPass> Gles2Rhi::create_render_pass(const rhi::RenderPassDesc& desc) +rhi::Handle<rhi::RenderPass> Gl2Rhi::create_render_pass(const rhi::RenderPassDesc& desc) { - SRB2_ASSERT(graphics_context_active_ == false); - // GL has no formal render pass object - return render_pass_slab_.insert(std::make_unique<Gles2RenderPass>(desc)); + Gl2RenderPass pass; + pass.desc = desc; + return render_pass_slab_.insert(std::move(pass)); } -void Gles2Rhi::destroy_render_pass(rhi::Handle<rhi::RenderPass>&& handle) +void Gl2Rhi::destroy_render_pass(rhi::Handle<rhi::RenderPass> handle) { - SRB2_ASSERT(graphics_context_active_ == false); - - std::unique_ptr<rhi::RenderPass> buffer = render_pass_slab_.remove(handle); - std::unique_ptr<Gles2RenderPass> casted(static_cast<Gles2RenderPass*>(buffer.release())); + render_pass_slab_.remove(handle); } -rhi::Handle<rhi::Texture> Gles2Rhi::create_texture( - const rhi::TextureDesc& desc, - srb2::rhi::PixelFormat data_format, - tcb::span<const std::byte> data -) +void Gl2Rhi::destroy_texture(rhi::Handle<rhi::Texture> handle) { - SRB2_ASSERT(graphics_context_active_ == false); - - GLint internal_format = map_texture_format(desc.format); - SRB2_ASSERT(internal_format != GL_ZERO); - - GLuint name = 0; - glGenTextures(1, &name); - - glBindTexture(GL_TEXTURE_2D, name); - - // if no data is provided, the initial texture is undefined - GLenum format = GL_RGBA; - GLenum type = GL_UNSIGNED_BYTE; - GLuint size = 0; - - const void* raw_data = nullptr; - std::tie(format, type, size) = map_pixel_data_format(data_format); - SRB2_ASSERT(format != GL_ZERO && type != GL_ZERO); - SRB2_ASSERT(internal_format == format); - if (!data.empty()) - { - SRB2_ASSERT(size * desc.width * desc.height == data.size_bytes()); - raw_data = static_cast<const void*>(data.data()); - } - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); - glTexImage2D(GL_TEXTURE_2D, 0, internal_format, desc.width, desc.height, 0, format, type, raw_data); - - return texture_slab_.insert(std::make_unique<Gles2Texture>(name, desc)); + SRB2_ASSERT(texture_slab_.is_valid(handle) == true); + Gl2Texture casted = texture_slab_.remove(handle); + GLuint name = casted.texture; + gl_->DeleteTextures(1, &name); + GL_ASSERT; } -void Gles2Rhi::destroy_texture(rhi::Handle<rhi::Texture>&& handle) +rhi::Handle<rhi::Texture> Gl2Rhi::create_texture(const rhi::TextureDesc& desc) { - SRB2_ASSERT(graphics_context_active_ == false); - - SRB2_ASSERT(texture_slab_.is_valid(handle) == true); - std::unique_ptr<Gles2Texture> casted = static_unique_ptr_cast<Gles2Texture>(texture_slab_.remove(handle)); - GLuint name = casted->texture; - disposal_.push_back([name] { glDeleteTextures(1, &name); }); + GLenum internal_format = map_internal_texture_format(desc.format); + SRB2_ASSERT(internal_format != GL_ZERO); + GLenum format = map_texture_format(desc.format); + + GLuint name = 0; + gl_->GenTextures(1, &name); + + gl_->BindTexture(GL_TEXTURE_2D, name); + + gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, map_texture_filter(desc.min)); + GL_ASSERT; + gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, map_texture_filter(desc.mag)); + GL_ASSERT; + gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, map_texture_wrap(desc.u_wrap)); + GL_ASSERT; + gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, map_texture_wrap(desc.v_wrap)); + GL_ASSERT; + gl_->TexImage2D(GL_TEXTURE_2D, 0, internal_format, desc.width, desc.height, 0, format, GL_UNSIGNED_BYTE, nullptr); + GL_ASSERT; + + Gl2Texture texture; + texture.texture = name; + texture.desc = desc; + return texture_slab_.insert(std::move(texture)); } -void Gles2Rhi::update_texture( +void Gl2Rhi::update_texture( + Handle<GraphicsContext> ctx, Handle<Texture> texture, Rect region, srb2::rhi::PixelFormat data_format, tcb::span<const std::byte> data ) { - SRB2_ASSERT(graphics_context_active_ == false); + SRB2_ASSERT(graphics_context_active_ == true); + + if (data.empty()) + { + return; + } SRB2_ASSERT(texture_slab_.is_valid(texture) == true); - auto& t = *static_cast<Gles2Texture*>(&texture_slab_[texture]); + auto& t = texture_slab_[texture]; + + // Each row of pixels must be on the unpack alignment boundary. + // This alignment is not user changeable until OpenGL 4. + constexpr const int32_t kUnpackAlignment = 4; GLenum format = GL_RGBA; GLenum type = GL_UNSIGNED_BYTE; @@ -560,12 +662,16 @@ void Gles2Rhi::update_texture( std::tie(format, type, size) = map_pixel_data_format(data_format); SRB2_ASSERT(format != GL_ZERO && type != GL_ZERO); SRB2_ASSERT(map_texture_format(t.desc.format) == format); - SRB2_ASSERT(region.w * region.h * size == data.size_bytes()); - SRB2_ASSERT(region.x + region.w < t.desc.width && region.y + region.h < t.desc.height); - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, t.texture); - glTexSubImage2D( + int32_t expected_row_span = (((size * region.w) + kUnpackAlignment - 1) / kUnpackAlignment) * kUnpackAlignment; + SRB2_ASSERT(expected_row_span * region.h == data.size_bytes()); + SRB2_ASSERT(region.x + region.w <= t.desc.width && region.y + region.h <= t.desc.height); + + gl_->ActiveTexture(GL_TEXTURE0); + GL_ASSERT; + gl_->BindTexture(GL_TEXTURE_2D, t.texture); + GL_ASSERT; + gl_->TexSubImage2D( GL_TEXTURE_2D, 0, region.x, @@ -576,15 +682,39 @@ void Gles2Rhi::update_texture( type, reinterpret_cast<const void*>(data.data()) ); + GL_ASSERT; } -rhi::Handle<rhi::Buffer> Gles2Rhi::create_buffer(const rhi::BufferDesc& desc, tcb::span<const std::byte> data) +void Gl2Rhi::update_texture_settings( + Handle<GraphicsContext> ctx, + Handle<Texture> texture, + TextureWrapMode u_wrap, + TextureWrapMode v_wrap, + TextureFilterMode min, + TextureFilterMode mag +) { - SRB2_ASSERT(graphics_context_active_ == false); + SRB2_ASSERT(graphics_context_active_ == true); - // If data is provided, it must match the buffer description size exactly - SRB2_ASSERT(data.size() != 0 ? data.size() == desc.size : true); + SRB2_ASSERT(texture_slab_.is_valid(texture) == true); + auto& t = texture_slab_[texture]; + + gl_->ActiveTexture(GL_TEXTURE0); + GL_ASSERT; + gl_->BindTexture(GL_TEXTURE_2D, t.texture); + GL_ASSERT; + gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, map_texture_wrap(u_wrap)); + GL_ASSERT; + gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, map_texture_wrap(v_wrap)); + GL_ASSERT; + gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, map_texture_filter(min)); + GL_ASSERT; + gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, map_texture_filter(mag)); + GL_ASSERT; +} +rhi::Handle<rhi::Buffer> Gl2Rhi::create_buffer(const rhi::BufferDesc& desc) +{ GLenum target = map_buffer_type(desc.type); SRB2_ASSERT(target != GL_ZERO); @@ -592,330 +722,510 @@ rhi::Handle<rhi::Buffer> Gles2Rhi::create_buffer(const rhi::BufferDesc& desc, tc SRB2_ASSERT(usage != GL_ZERO); GLuint name = 0; - glGenBuffers(1, &name); + gl_->GenBuffers(1, &name); + GL_ASSERT; - glBindBuffer(target, name); + gl_->BindBuffer(target, name); + GL_ASSERT; - // if no data is provided, the initial buffer data is undefined - const void* raw_data = nullptr; - if (!data.empty()) - { - raw_data = static_cast<const void*>(data.data()); - } - glBufferData(target, desc.size, raw_data, usage); + gl_->BufferData(target, desc.size, nullptr, usage); + GL_ASSERT; - return buffer_slab_.insert(std::make_unique<Gles2Buffer>(name, desc)); + Gl2Buffer buffer; + buffer.buffer = name; + buffer.desc = desc; + return buffer_slab_.insert(std::move(buffer)); } -void Gles2Rhi::destroy_buffer(rhi::Handle<rhi::Buffer>&& handle) +void Gl2Rhi::destroy_buffer(rhi::Handle<rhi::Buffer> handle) { - SRB2_ASSERT(graphics_context_active_ == false); - SRB2_ASSERT(buffer_slab_.is_valid(handle) == true); - SRB2_ASSERT(graphics_context_active_ == false); - std::unique_ptr<Gles2Buffer> casted = static_unique_ptr_cast<Gles2Buffer>(buffer_slab_.remove(handle)); - GLuint name = casted->buffer; + Gl2Buffer casted = buffer_slab_.remove(handle); + GLuint name = casted.buffer; - disposal_.push_back([name] { glDeleteBuffers(1, &name); }); + gl_->DeleteBuffers(1, &name); + GL_ASSERT; } -void Gles2Rhi::update_buffer_contents(rhi::Handle<rhi::Buffer> handle, uint32_t offset, tcb::span<const std::byte> data) +void Gl2Rhi::update_buffer( + rhi::Handle<GraphicsContext> ctx, + rhi::Handle<rhi::Buffer> handle, + uint32_t offset, + tcb::span<const std::byte> data +) { - SRB2_ASSERT(graphics_context_active_ == false); - - SRB2_ASSERT(buffer_slab_.is_valid(handle) == true); - auto& b = *static_cast<Gles2Buffer*>(&buffer_slab_[handle]); + SRB2_ASSERT(graphics_context_active_ == true); + SRB2_ASSERT(ctx.generation() == graphics_context_generation_); - if (data.size() == 0) + if (data.empty()) + { return; + } + + SRB2_ASSERT(buffer_slab_.is_valid(handle) == true); + auto& b = buffer_slab_[handle]; - SRB2_ASSERT(offset < b.desc.size && offset + data.size() < b.desc.size); + SRB2_ASSERT(offset < b.desc.size && offset + data.size() <= b.desc.size); + GLenum target = GL_ZERO; switch (b.desc.type) { case rhi::BufferType::kVertexBuffer: - glBindBuffer(GL_ARRAY_BUFFER, b.buffer); - glBufferSubData(GL_ARRAY_BUFFER, offset, data.size(), data.data()); + target = GL_ARRAY_BUFFER; break; case rhi::BufferType::kIndexBuffer: - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, b.buffer); - glBufferSubData(GL_ARRAY_BUFFER, offset, data.size(), data.data()); + target = GL_ELEMENT_ARRAY_BUFFER; break; } + + gl_->BindBuffer(target, b.buffer); + GL_ASSERT; + gl_->BufferSubData(target, offset, data.size(), data.data()); + GL_ASSERT; } -rhi::Handle<rhi::Renderbuffer> Gles2Rhi::create_renderbuffer(const rhi::RenderbufferDesc& desc) +rhi::Handle<rhi::UniformSet> +Gl2Rhi::create_uniform_set(rhi::Handle<rhi::GraphicsContext> ctx, const rhi::CreateUniformSetInfo& info) { - SRB2_ASSERT(graphics_context_active_ == false); + SRB2_ASSERT(graphics_context_active_ == true); + SRB2_ASSERT(ctx.generation() == graphics_context_generation_); - GLuint name = 0; - glGenRenderbuffers(1, &name); + Gl2UniformSet uniform_set; - // Obtain storage up-front. - glBindRenderbuffer(GL_RENDERBUFFER, name); - glRenderbufferStorage(GL_RENDERBUFFER, map_pixel_format(desc.format), desc.width, desc.height); + for (auto& uniform : info.uniforms) + { + uniform_set.uniforms.push_back(uniform); + } - return renderbuffer_slab_.insert(std::make_unique<Gles2Renderbuffer>(Gles2Renderbuffer {name})); + return uniform_set_slab_.insert(std::move(uniform_set)); } -void Gles2Rhi::destroy_renderbuffer(rhi::Handle<rhi::Renderbuffer>&& handle) -{ - SRB2_ASSERT(graphics_context_active_ == false); - - SRB2_ASSERT(renderbuffer_slab_.is_valid(handle) == true); - std::unique_ptr<Gles2Renderbuffer> casted = - static_unique_ptr_cast<Gles2Renderbuffer>(renderbuffer_slab_.remove(handle)); - GLuint name = casted->renderbuffer; - disposal_.push_back([name] { glDeleteRenderbuffers(1, &name); }); -} - -rhi::Handle<rhi::Pipeline> Gles2Rhi::create_pipeline(const PipelineDesc& desc) +rhi::Handle<rhi::BindingSet> Gl2Rhi::create_binding_set( + rhi::Handle<rhi::GraphicsContext> ctx, + Handle<Pipeline> pipeline, + const rhi::CreateBindingSetInfo& info +) { - SRB2_ASSERT(platform_ != nullptr); - // TODO assert compatibility of pipeline description with program using ProgramRequirements - - GLuint vertex = 0; - GLuint fragment = 0; - GLuint program = 0; - Gles2Pipeline pipeline; - - auto [vert_src, frag_src] = platform_->find_shader_sources(desc.program); - - // TODO preprocess shader code with specialization defines based on pipeline configuration - - const char* vert_src_arr[1] = {vert_src.c_str()}; - const GLint vert_src_arr_lens[1] = {static_cast<GLint>(vert_src.size())}; - const char* frag_src_arr[1] = {frag_src.c_str()}; - const GLint frag_src_arr_lens[1] = {static_cast<GLint>(frag_src.size())}; + SRB2_ASSERT(graphics_context_active_ == true); + SRB2_ASSERT(ctx.generation() == graphics_context_generation_); - vertex = glCreateShader(GL_VERTEX_SHADER); - glShaderSource(vertex, 1, vert_src_arr, vert_src_arr_lens); - glCompileShader(vertex); - GLint is_compiled = 0; - glGetShaderiv(vertex, GL_COMPILE_STATUS, &is_compiled); - if (is_compiled == GL_FALSE) - { - GLint max_length = 0; - glGetShaderiv(vertex, GL_INFO_LOG_LENGTH, &max_length); - std::vector<GLchar> compile_error(max_length); - glGetShaderInfoLog(vertex, max_length, &max_length, compile_error.data()); + SRB2_ASSERT(pipeline_slab_.is_valid(pipeline) == true); + auto& pl = pipeline_slab_[pipeline]; - glDeleteShader(vertex); - throw std::runtime_error(std::string("Vertex shader compilation failed: ") + std::string(compile_error.data())); - } - fragment = glCreateShader(GL_FRAGMENT_SHADER); - glShaderSource(fragment, 1, frag_src_arr, frag_src_arr_lens); - glCompileShader(fragment); - glGetShaderiv(vertex, GL_COMPILE_STATUS, &is_compiled); - if (is_compiled == GL_FALSE) - { - GLint max_length = 0; - glGetShaderiv(fragment, GL_INFO_LOG_LENGTH, &max_length); - std::vector<GLchar> compile_error(max_length); - glGetShaderInfoLog(fragment, max_length, &max_length, compile_error.data()); - - glDeleteShader(fragment); - glDeleteShader(vertex); - throw std::runtime_error( - std::string("Fragment shader compilation failed: ") + std::string(compile_error.data()) - ); - } - program = glCreateProgram(); - glAttachShader(program, vertex); - glAttachShader(program, fragment); - glLinkProgram(program); - glGetProgramiv(program, GL_LINK_STATUS, &is_compiled); - if (is_compiled == GL_FALSE) - { - GLint max_length = 0; - glGetProgramiv(program, GL_INFO_LOG_LENGTH, &max_length); - std::vector<GLchar> link_error(max_length); - glGetProgramInfoLog(program, max_length, &max_length, link_error.data()); - - glDeleteProgram(program); - glDeleteShader(fragment); - glDeleteShader(vertex); - throw std::runtime_error(std::string("Pipeline program link failed: ") + std::string(link_error.data())); - } + SRB2_ASSERT(info.vertex_buffers.size() == pl.desc.vertex_input.buffer_layouts.size()); - std::unordered_map<std::string, Gles2ActiveUniform> active_attributes; - GLint active_attribute_total = -1; - glGetProgramiv(program, GL_ACTIVE_ATTRIBUTES, &active_attribute_total); - if (active_attribute_total < 0) - { - glDeleteProgram(program); - glDeleteShader(fragment); - glDeleteShader(vertex); - throw std::runtime_error("Unable to retrieve program active attributes"); - } - if (desc.vertex_input.attr_layouts.size() != static_cast<GLuint>(active_attribute_total)) - { - glDeleteProgram(program); - glDeleteShader(fragment); - glDeleteShader(vertex); - std::string ex_msg("Pipeline's enabled attribute count does not match the linked program's total: "); - ex_msg.append(std::to_string(desc.vertex_input.attr_layouts.size())); - ex_msg.append(" vs "); - ex_msg.append(std::to_string(static_cast<GLuint>(active_attribute_total))); - throw std::runtime_error(std::move(ex_msg)); - } - for (GLint i = 0; i < active_attribute_total; i++) - { - GLsizei name_len = 0; - GLint size = 0; - GLenum type = GL_ZERO; - char name[256]; - glGetActiveAttrib(program, i, 255, &name_len, &size, &type, name); - active_attributes.insert({std::string(name), Gles2ActiveUniform {type, static_cast<GLuint>(i)}}); - } + Gl2BindingSet binding_set; - std::unordered_map<std::string, Gles2ActiveUniform> active_uniforms; - GLint active_uniform_total = -1; - glGetProgramiv(program, GL_ACTIVE_UNIFORMS, &active_uniform_total); - if (active_uniform_total < 0) - { - glDeleteProgram(program); - glDeleteShader(fragment); - glDeleteShader(vertex); - throw std::runtime_error("Unable to retrieve program active uniforms"); - } - if (desc.uniform_input.enabled_uniforms.size() + desc.sampler_input.enabled_samplers.size() != - static_cast<GLuint>(active_uniform_total)) - { - glDeleteProgram(program); - glDeleteShader(fragment); - glDeleteShader(vertex); - std::string ex_msg( - "Pipeline's enabled uniform count (uniforms + samplers) does not match the linked program's total: " - ); - ex_msg.append(std::to_string(desc.uniform_input.enabled_uniforms.size())); - ex_msg.append(" vs "); - ex_msg.append(std::to_string(static_cast<GLuint>(active_uniform_total))); - throw std::runtime_error(std::move(ex_msg)); - } - for (GLint i = 0; i < active_uniform_total; i++) + for (auto& vertex_buffer : info.vertex_buffers) { - GLsizei name_len = 0; - GLint size = 0; - GLenum type = GL_ZERO; - char name[256]; - glGetActiveUniform(program, i, 255, &name_len, &size, &type, name); - active_uniforms.insert({std::string(name), Gles2ActiveUniform {type, static_cast<GLuint>(i)}}); + binding_set.vertex_buffer_bindings.push_back(vertex_buffer); } - for (auto& attr : desc.vertex_input.attr_layouts) + // Set textures + for (size_t i = 0; i < info.sampler_textures.size(); i++) { - const char* symbol_name = map_vertex_attribute_symbol_name(attr.name); - SRB2_ASSERT(symbol_name != nullptr); - if (active_attributes.find(symbol_name) == active_attributes.end()) - { - glDeleteProgram(program); - glDeleteShader(fragment); - glDeleteShader(vertex); - throw std::runtime_error("Enabled attribute not found in linked program"); - } - auto& active_attr = active_attributes[symbol_name]; - auto expected_format = rhi::vertex_attribute_format(attr.name); - auto expected_gl_type = map_vertex_attribute_format(expected_format); - SRB2_ASSERT(expected_gl_type != GL_ZERO); - if (expected_gl_type != active_attr.type) - { - glDeleteProgram(program); - glDeleteShader(fragment); - glDeleteShader(vertex); - throw std::runtime_error("Active attribute type does not match expected type"); - } + auto& binding = info.sampler_textures[i]; + auto& sampler_name = pl.desc.sampler_input.enabled_samplers[i]; + SRB2_ASSERT(binding.name == sampler_name); - pipeline.attrib_locations.insert({attr.name, active_attr.location}); + SRB2_ASSERT(texture_slab_.is_valid(binding.texture)); + auto& tx = texture_slab_[binding.texture]; + binding_set.textures.insert({sampler_name, tx.texture}); } - for (auto& uniform : desc.uniform_input.enabled_uniforms) - { - const char* symbol_name = map_uniform_attribute_symbol_name(uniform); - SRB2_ASSERT(symbol_name != nullptr); - if (active_uniforms.find(symbol_name) == active_uniforms.end()) - { - glDeleteProgram(program); - glDeleteShader(fragment); - glDeleteShader(vertex); - throw std::runtime_error("Enabled uniform not found in linked program"); - } - auto& active_uniform = active_uniforms[symbol_name]; - auto expected_format = rhi::uniform_format(uniform); - auto expected_gl_type = map_uniform_format(expected_format); - SRB2_ASSERT(expected_gl_type != GL_ZERO); - if (expected_gl_type != active_uniform.type) - { - glDeleteProgram(program); - glDeleteShader(fragment); - glDeleteShader(vertex); - throw std::runtime_error("Active uniform type does not match expected type"); - } - pipeline.uniform_locations.insert({uniform, active_uniform.location}); - } - for (auto& sampler : desc.sampler_input.enabled_samplers) - { - const char* symbol_name = map_sampler_symbol_name(sampler); - SRB2_ASSERT(symbol_name != nullptr); - if (active_uniforms.find(symbol_name) == active_uniforms.end()) - { - glDeleteProgram(program); - glDeleteShader(fragment); - glDeleteShader(vertex); - throw std::runtime_error("Enabled sampler not found in linked program"); - } - auto& active_sampler = active_uniforms[symbol_name]; - if (active_sampler.type != GL_SAMPLER_2D) - { - glDeleteProgram(program); - glDeleteShader(fragment); - glDeleteShader(vertex); - throw std::runtime_error("Active sampler type does not match expected type"); - } + return binding_set_slab_.insert(std::move(binding_set)); +} - pipeline.sampler_locations.insert({sampler, active_sampler.location}); - } +rhi::Handle<rhi::Renderbuffer> Gl2Rhi::create_renderbuffer(const rhi::RenderbufferDesc& desc) +{ + GLuint name = 0; + gl_->GenRenderbuffers(1, &name); - pipeline.desc = desc; - pipeline.vertex_shader = vertex; - pipeline.fragment_shader = fragment; - pipeline.program = program; + // Obtain storage up-front. + gl_->BindRenderbuffer(GL_RENDERBUFFER, name); + GL_ASSERT; + + // For consistency, while RHI does not specify the bit size of the depth or stencil components, + // nor if they are packed or separate, each backend should be expected to create a packed depth-stencil + // D24S8 format image. + // This is despite modern AMD apparently not supporting this format in hardware. It ensures the + // depth behavior between backends is the same. We should not brush up against performance issues in practice. + + // - GL Core requires both D24S8 and D32FS8 format support. + // - GL 2 via ARB_framebuffer_object requires D24S8. Backend must require this extension. + // - GLES 2 via OES_packed_depth_stencil requires D24S8. Backend must require this extension. + // - Vulkan requires **one of** D24S8 or D32FS8. The backend must decide which format to use based on caps. + // (Even if D32FS8 is available, D24S8 should be preferred) + + // For reference, D32FS8 at 4k requires 64 MiB of linear memory. D24S8 is 32 MiB. + + gl_->RenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8_OES, desc.width, desc.height); + GL_ASSERT; // was + + Gl2Renderbuffer rb; + rb.renderbuffer = name; + rb.desc = desc; + return renderbuffer_slab_.insert(std::move(rb)); +} - return pipeline_slab_.insert(std::make_unique<Gles2Pipeline>(std::move(pipeline))); +void Gl2Rhi::destroy_renderbuffer(rhi::Handle<rhi::Renderbuffer> handle) +{ + SRB2_ASSERT(renderbuffer_slab_.is_valid(handle) == true); + Gl2Renderbuffer casted = renderbuffer_slab_.remove(handle); + GLuint name = casted.renderbuffer; + gl_->DeleteRenderbuffers(1, &name); + GL_ASSERT; } -void Gles2Rhi::destroy_pipeline(rhi::Handle<rhi::Pipeline>&& handle) +rhi::Handle<rhi::Pipeline> Gl2Rhi::create_pipeline(const PipelineDesc& desc) { - SRB2_ASSERT(graphics_context_active_ == false); + SRB2_ASSERT(platform_ != nullptr); + // TODO assert compatibility of pipeline description with program using ProgramRequirements + + const rhi::ProgramRequirements& reqs = rhi::program_requirements_for_program(desc.program); + + GLuint vertex = 0; + GLuint fragment = 0; + GLuint program = 0; + Gl2Pipeline pipeline; + + auto [vert_srcs, frag_srcs] = platform_->find_shader_sources(desc.program); + + + // GL 2 note: + // Do not explicitly set GLSL version. Unversioned sources are required to be treated as 110, but writing 110 + // breaks the AMD driver's program linker in a bizarre way. + + // Process vertex shader sources + std::vector<const char*> vert_sources; + ShaderLoadContext vert_ctx; + vert_ctx.set_version("100"); + for (auto& attribute : desc.vertex_input.attr_layouts) + { + for (auto const& require_attr : reqs.vertex_input.attributes) + { + if (require_attr.name == attribute.name && !require_attr.required) + { + vert_ctx.define(map_vertex_attribute_enable_define(attribute.name)); + } + } + } + for (auto& uniform_group : desc.uniform_input.enabled_uniforms) + { + for (auto& uniform : uniform_group) + { + for (auto const& req_uni_group : reqs.uniforms.uniform_groups) + { + for (auto const& req_uni : req_uni_group) + { + if (req_uni.name == uniform && !req_uni.required) + { + vert_ctx.define(map_uniform_enable_define(uniform)); + } + } + } + } + } + for (auto& src : vert_srcs) + { + vert_ctx.add_source(std::move(src)); + } + vert_sources = vert_ctx.get_sources_array(); + + // Process vertex shader sources + std::vector<const char*> frag_sources; + ShaderLoadContext frag_ctx; + frag_ctx.set_version("100"); + for (auto& sampler : desc.sampler_input.enabled_samplers) + { + for (auto const& require_sampler : reqs.samplers.samplers) + { + if (sampler == require_sampler.name && !require_sampler.required) + { + frag_ctx.define(map_sampler_enable_define(sampler)); + } + } + } + for (auto& uniform_group : desc.uniform_input.enabled_uniforms) + { + for (auto& uniform : uniform_group) + { + for (auto const& req_uni_group : reqs.uniforms.uniform_groups) + { + for (auto const& req_uni : req_uni_group) + { + if (req_uni.name == uniform && !req_uni.required) + { + frag_ctx.define(map_uniform_enable_define(uniform)); + } + } + } + } + } + for (auto& src : frag_srcs) + { + frag_ctx.add_source(std::move(src)); + } + frag_sources = frag_ctx.get_sources_array(); + + vertex = gl_->CreateShader(GL_VERTEX_SHADER); + gl_->ShaderSource(vertex, vert_sources.size(), vert_sources.data(), NULL); + gl_->CompileShader(vertex); + GLint is_compiled = 0; + gl_->GetShaderiv(vertex, GL_COMPILE_STATUS, &is_compiled); + if (is_compiled == GL_FALSE) + { + GLint max_length = 0; + gl_->GetShaderiv(vertex, GL_INFO_LOG_LENGTH, &max_length); + std::vector<GLchar> compile_error(max_length); + gl_->GetShaderInfoLog(vertex, max_length, &max_length, compile_error.data()); + + gl_->DeleteShader(vertex); + throw std::runtime_error(fmt::format("Vertex shader compilation failed: {}", std::string(compile_error.data())) + ); + } + fragment = gl_->CreateShader(GL_FRAGMENT_SHADER); + gl_->ShaderSource(fragment, frag_sources.size(), frag_sources.data(), NULL); + gl_->CompileShader(fragment); + gl_->GetShaderiv(vertex, GL_COMPILE_STATUS, &is_compiled); + if (is_compiled == GL_FALSE) + { + GLint max_length = 0; + gl_->GetShaderiv(fragment, GL_INFO_LOG_LENGTH, &max_length); + std::vector<GLchar> compile_error(max_length); + gl_->GetShaderInfoLog(fragment, max_length, &max_length, compile_error.data()); + + gl_->DeleteShader(fragment); + gl_->DeleteShader(vertex); + throw std::runtime_error( + fmt::format("Fragment shader compilation failed: {}", std::string(compile_error.data())) + ); + } + + // Program link + + program = gl_->CreateProgram(); + gl_->AttachShader(program, vertex); + gl_->AttachShader(program, fragment); + gl_->LinkProgram(program); + gl_->GetProgramiv(program, GL_LINK_STATUS, &is_compiled); + if (is_compiled == GL_FALSE) + { + GLint max_length = 0; + gl_->GetProgramiv(program, GL_INFO_LOG_LENGTH, &max_length); + std::vector<GLchar> link_error(max_length); + gl_->GetProgramInfoLog(program, max_length, &max_length, link_error.data()); + + gl_->DeleteProgram(program); + gl_->DeleteShader(fragment); + gl_->DeleteShader(vertex); + throw std::runtime_error(fmt::format("Pipeline program link failed: {}", std::string(link_error.data()))); + } + + std::unordered_map<std::string, Gl2ActiveUniform> active_attributes; + GLint active_attribute_total = -1; + gl_->GetProgramiv(program, GL_ACTIVE_ATTRIBUTES, &active_attribute_total); + if (active_attribute_total < 0) + { + gl_->DeleteProgram(program); + gl_->DeleteShader(fragment); + gl_->DeleteShader(vertex); + throw std::runtime_error("Unable to retrieve program active attributes"); + } + if (desc.vertex_input.attr_layouts.size() != static_cast<GLuint>(active_attribute_total)) + { + gl_->DeleteProgram(program); + gl_->DeleteShader(fragment); + gl_->DeleteShader(vertex); + throw std::runtime_error(fmt::format( + "Pipeline's enabled attribute count does not match the linked program's total: {} vs {}", + desc.vertex_input.attr_layouts.size(), + static_cast<GLuint>(active_attribute_total) + )); + } + for (GLint i = 0; i < active_attribute_total; i++) + { + GLsizei name_len = 0; + GLint size = 0; + GLenum type = GL_ZERO; + char name[256]; + gl_->GetActiveAttrib(program, i, 255, &name_len, &size, &type, name); + GL_ASSERT; + GLint location = gl_->GetAttribLocation(program, name); + GL_ASSERT; + active_attributes.insert({std::string(name), Gl2ActiveUniform {type, static_cast<GLuint>(location)}}); + } + + std::unordered_map<std::string, Gl2ActiveUniform> active_uniforms; + size_t total_enabled_uniforms = 0; + for (auto g = desc.uniform_input.enabled_uniforms.cbegin(); g != desc.uniform_input.enabled_uniforms.cend(); + g = std::next(g)) + { + total_enabled_uniforms += g->size(); + } + GLint active_uniform_total = -1; + gl_->GetProgramiv(program, GL_ACTIVE_UNIFORMS, &active_uniform_total); + if (active_uniform_total < 0) + { + gl_->DeleteProgram(program); + gl_->DeleteShader(fragment); + gl_->DeleteShader(vertex); + throw std::runtime_error("Unable to retrieve program active uniforms"); + } + if (total_enabled_uniforms + desc.sampler_input.enabled_samplers.size() != + static_cast<GLuint>(active_uniform_total)) + { + gl_->DeleteProgram(program); + gl_->DeleteShader(fragment); + gl_->DeleteShader(vertex); + throw std::runtime_error(fmt::format( + "Pipeline's enabled uniform count (uniforms + samplers) does not match the linked program's total: {} vs " + "{}", + total_enabled_uniforms + desc.sampler_input.enabled_samplers.size(), + static_cast<GLuint>(active_uniform_total) + )); + } + for (GLint i = 0; i < active_uniform_total; i++) + { + GLsizei name_len = 0; + GLint size = 0; + GLenum type = GL_ZERO; + char name[256]; + gl_->GetActiveUniform(program, i, 255, &name_len, &size, &type, name); + GL_ASSERT; + GLint location = gl_->GetUniformLocation(program, name); + GL_ASSERT; + active_uniforms.insert({std::string(name), Gl2ActiveUniform {type, static_cast<GLuint>(location)}}); + } + + for (auto& attr : desc.vertex_input.attr_layouts) + { + const char* symbol_name = map_vertex_attribute_symbol_name(attr.name); + SRB2_ASSERT(symbol_name != nullptr); + if (active_attributes.find(symbol_name) == active_attributes.end()) + { + gl_->DeleteProgram(program); + gl_->DeleteShader(fragment); + gl_->DeleteShader(vertex); + throw std::runtime_error("Enabled attribute not found in linked program"); + } + auto& active_attr = active_attributes[symbol_name]; + auto expected_format = rhi::vertex_attribute_format(attr.name); + auto expected_gl_type = map_vertex_attribute_format(expected_format); + SRB2_ASSERT(expected_gl_type != GL_ZERO); + if (expected_gl_type != active_attr.type) + { + gl_->DeleteProgram(program); + gl_->DeleteShader(fragment); + gl_->DeleteShader(vertex); + throw std::runtime_error("Active attribute type does not match expected type"); + } + + pipeline.attrib_locations.insert({attr.name, active_attr.location}); + } + + for (auto group_itr = desc.uniform_input.enabled_uniforms.cbegin(); + group_itr != desc.uniform_input.enabled_uniforms.cend(); + group_itr = std::next(group_itr)) + { + auto& group = *group_itr; + for (auto itr = group.cbegin(); itr != group.cend(); itr = std::next(itr)) + { + auto& uniform = *itr; + const char* symbol_name = map_uniform_attribute_symbol_name(uniform); + SRB2_ASSERT(symbol_name != nullptr); + if (active_uniforms.find(symbol_name) == active_uniforms.end()) + { + gl_->DeleteProgram(program); + gl_->DeleteShader(fragment); + gl_->DeleteShader(vertex); + throw std::runtime_error("Enabled uniform not found in linked program"); + } + auto& active_uniform = active_uniforms[symbol_name]; + auto expected_format = rhi::uniform_format(uniform); + auto expected_gl_type = map_uniform_format(expected_format); + SRB2_ASSERT(expected_gl_type != GL_ZERO); + if (expected_gl_type != active_uniform.type) + { + gl_->DeleteProgram(program); + gl_->DeleteShader(fragment); + gl_->DeleteShader(vertex); + throw std::runtime_error("Active uniform type does not match expected type"); + } + SRB2_ASSERT(pipeline.uniform_locations.find(uniform) == pipeline.uniform_locations.end()); + pipeline.uniform_locations.insert({uniform, active_uniform.location}); + } + } + + for (auto& sampler : desc.sampler_input.enabled_samplers) + { + const char* symbol_name = map_sampler_symbol_name(sampler); + SRB2_ASSERT(symbol_name != nullptr); + if (active_uniforms.find(symbol_name) == active_uniforms.end()) + { + gl_->DeleteProgram(program); + gl_->DeleteShader(fragment); + gl_->DeleteShader(vertex); + throw std::runtime_error("Enabled sampler not found in linked program"); + } + auto& active_sampler = active_uniforms[symbol_name]; + if (active_sampler.type != GL_SAMPLER_2D) + { + gl_->DeleteProgram(program); + gl_->DeleteShader(fragment); + gl_->DeleteShader(vertex); + throw std::runtime_error("Active sampler type does not match expected type"); + } + + pipeline.sampler_locations.insert({sampler, active_sampler.location}); + } + + pipeline.desc = desc; + pipeline.vertex_shader = vertex; + pipeline.fragment_shader = fragment; + pipeline.program = program; + + return pipeline_slab_.insert(std::move(pipeline)); +} +void Gl2Rhi::destroy_pipeline(rhi::Handle<rhi::Pipeline> handle) +{ SRB2_ASSERT(pipeline_slab_.is_valid(handle) == true); - std::unique_ptr<Gles2Pipeline> casted = static_unique_ptr_cast<Gles2Pipeline>(pipeline_slab_.remove(handle)); - GLuint vertex_shader = casted->vertex_shader; - GLuint fragment_shader = casted->fragment_shader; - GLuint program = casted->program; - - disposal_.push_back([=] { glDeleteShader(fragment_shader); }); - disposal_.push_back([=] { glDeleteShader(vertex_shader); }); - disposal_.push_back([=] { glDeleteProgram(program); }); + Gl2Pipeline casted = pipeline_slab_.remove(handle); + GLuint vertex_shader = casted.vertex_shader; + GLuint fragment_shader = casted.fragment_shader; + GLuint program = casted.program; + + gl_->DeleteProgram(program); + GL_ASSERT; + gl_->DeleteShader(vertex_shader); + GL_ASSERT; + gl_->DeleteShader(fragment_shader); + GL_ASSERT; } -rhi::Handle<rhi::GraphicsContext> Gles2Rhi::begin_graphics() +rhi::Handle<rhi::GraphicsContext> Gl2Rhi::begin_graphics() { SRB2_ASSERT(graphics_context_active_ == false); graphics_context_active_ = true; return rhi::Handle<rhi::GraphicsContext>(0, graphics_context_generation_); } -void Gles2Rhi::end_graphics(rhi::Handle<rhi::GraphicsContext>&& handle) +void Gl2Rhi::end_graphics(rhi::Handle<rhi::GraphicsContext> handle) { SRB2_ASSERT(graphics_context_active_ == true); SRB2_ASSERT(current_pipeline_.has_value() == false && current_render_pass_.has_value() == false); graphics_context_generation_ += 1; + if (graphics_context_generation_ == 0) + { + graphics_context_generation_ = 1; + } graphics_context_active_ = false; - glFlush(); + gl_->Flush(); + GL_ASSERT; } -void Gles2Rhi::present() +void Gl2Rhi::present() { SRB2_ASSERT(platform_ != nullptr); SRB2_ASSERT(graphics_context_active_ == false); @@ -923,7 +1233,7 @@ void Gles2Rhi::present() platform_->present(); } -void Gles2Rhi::begin_default_render_pass(Handle<GraphicsContext> ctx) +void Gl2Rhi::begin_default_render_pass(Handle<GraphicsContext> ctx, bool clear) { SRB2_ASSERT(platform_ != nullptr); SRB2_ASSERT(graphics_context_active_ == true); @@ -931,57 +1241,108 @@ void Gles2Rhi::begin_default_render_pass(Handle<GraphicsContext> ctx) const Rect fb_rect = platform_->get_default_framebuffer_dimensions(); - glBindFramebuffer(GL_FRAMEBUFFER, 0); - glViewport(0, 0, fb_rect.w, fb_rect.h); + gl_->BindFramebuffer(GL_FRAMEBUFFER, 0); + GL_ASSERT; + gl_->Disable(GL_SCISSOR_TEST); + GL_ASSERT; + gl_->Viewport(0, 0, fb_rect.w, fb_rect.h); + GL_ASSERT; - glClearColor(0.5f, 0.5f, 0.5f, 1.0f); - glClearDepthf(1.0f); - glClearStencil(0); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + if (clear) + { + gl_->ClearColor(0.0f, 0.0f, 0.0f, 1.0f); + gl_->ClearDepthf(1.0f); + gl_->ClearStencil(0); + gl_->Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + GL_ASSERT; + } - current_render_pass_ = Gles2Rhi::DefaultRenderPassState {}; + current_render_pass_ = Gl2Rhi::DefaultRenderPassState {}; } +#include <iostream> -void Gles2Rhi::begin_render_pass(Handle<GraphicsContext> ctx, const RenderPassBeginInfo& info) +void Gl2Rhi::begin_render_pass(Handle<GraphicsContext> ctx, const RenderPassBeginInfo& info) { SRB2_ASSERT(graphics_context_active_ == true && graphics_context_generation_ == ctx.generation()); SRB2_ASSERT(current_render_pass_.has_value() == false); SRB2_ASSERT(render_pass_slab_.is_valid(info.render_pass) == true); - auto& rp = *static_cast<Gles2RenderPass*>(&render_pass_slab_[info.render_pass]); - SRB2_ASSERT(rp.desc.depth_format.has_value() == info.depth_attachment.has_value()); + auto& rp = render_pass_slab_[info.render_pass]; + SRB2_ASSERT(rp.desc.use_depth_stencil == info.depth_stencil_attachment.has_value()); - auto fb_itr = framebuffers_.find(Gles2FramebufferKey {info.color_attachment, info.depth_attachment}); + auto fb_itr = framebuffers_.find(Gl2FramebufferKey {info.color_attachment, info.depth_stencil_attachment}); if (fb_itr == framebuffers_.end()) { // Create a new framebuffer for this color-depth pair GLuint fb_name; - glGenFramebuffers(1, &fb_name); - glBindFramebuffer(GL_FRAMEBUFFER, fb_name); - fb_itr = - framebuffers_ - .insert( - {Gles2FramebufferKey {info.color_attachment, info.depth_attachment}, static_cast<uint32_t>(fb_name)} - ) - .first; - - // TODO bind buffers correctly + SRB2_ASSERT(gl_->GenFramebuffers!=NULL); + gl_->GenFramebuffers(1, &fb_name); + GL_ASSERT; + gl_->BindFramebuffer(GL_FRAMEBUFFER, fb_name); + GL_ASSERT; + fb_itr = framebuffers_ + .insert( + {Gl2FramebufferKey {info.color_attachment, info.depth_stencil_attachment}, + static_cast<uint32_t>(fb_name)} + ) + .first; + + SRB2_ASSERT(texture_slab_.is_valid(info.color_attachment)); + auto& texture = texture_slab_[info.color_attachment]; + SRB2_ASSERT(texture.desc.format == TextureFormat::kRGBA); + gl_->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture.texture, 0); + GL_ASSERT; + + if (rp.desc.use_depth_stencil && info.depth_stencil_attachment.has_value()) + { + SRB2_ASSERT(renderbuffer_slab_.is_valid(*info.depth_stencil_attachment)); + auto& renderbuffer = renderbuffer_slab_[*info.depth_stencil_attachment]; + gl_->FramebufferRenderbuffer( + GL_FRAMEBUFFER, + GL_DEPTH_ATTACHMENT, + GL_RENDERBUFFER, + renderbuffer.renderbuffer + ); + GL_ASSERT; + } } auto& fb = *fb_itr; - glBindFramebuffer(GL_FRAMEBUFFER, fb.second); + gl_->BindFramebuffer(GL_FRAMEBUFFER, fb.second); + GL_ASSERT; + gl_->Disable(GL_SCISSOR_TEST); + GL_ASSERT; + + GLint clear_bits = 0; + if (rp.desc.color_load_op == rhi::AttachmentLoadOp::kClear) + { + gl_->ClearColor(info.clear_color.r, info.clear_color.g, info.clear_color.b, info.clear_color.a); + clear_bits |= GL_COLOR_BUFFER_BIT; + } + + if (rp.desc.use_depth_stencil) + { + if (rp.desc.depth_load_op == rhi::AttachmentLoadOp::kClear) + { + gl_->ClearDepthf(1.0f); + clear_bits |= GL_DEPTH_BUFFER_BIT; + } + if (rp.desc.stencil_load_op == rhi::AttachmentLoadOp::kClear) + { + gl_->ClearStencil(0); + clear_bits |= GL_STENCIL_BUFFER_BIT; + } + } - if (rp.desc.load_op == rhi::AttachmentLoadOp::kClear) + if (clear_bits != 0) { - glClearColor(info.clear_color.r, info.clear_color.g, info.clear_color.b, info.clear_color.a); - glClearDepthf(1.f); - glClearStencil(0); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + gl_->Clear(clear_bits); + GL_ASSERT; } current_render_pass_ = info; } -void Gles2Rhi::end_render_pass(Handle<GraphicsContext> ctx) +void Gl2Rhi::end_render_pass(Handle<GraphicsContext> ctx) { SRB2_ASSERT(graphics_context_active_ == true && graphics_context_generation_ == ctx.generation()); SRB2_ASSERT(current_render_pass_.has_value() == true); @@ -990,94 +1351,266 @@ void Gles2Rhi::end_render_pass(Handle<GraphicsContext> ctx) current_render_pass_ = std::nullopt; } -void Gles2Rhi::bind_pipeline(Handle<GraphicsContext> ctx, Handle<Pipeline> pipeline) +void Gl2Rhi::bind_pipeline(Handle<GraphicsContext> ctx, Handle<Pipeline> pipeline) { SRB2_ASSERT(graphics_context_active_ == true && graphics_context_generation_ == ctx.generation()); SRB2_ASSERT(current_render_pass_.has_value() == true); SRB2_ASSERT(pipeline_slab_.is_valid(pipeline) == true); - auto& pl = *static_cast<Gles2Pipeline*>(&pipeline_slab_[pipeline]); + auto& pl = pipeline_slab_[pipeline]; auto& desc = pl.desc; - glUseProgram(pl.program); + gl_->UseProgram(pl.program); + GL_ASSERT; - glDisable(GL_SCISSOR_TEST); + gl_->Disable(GL_SCISSOR_TEST); + GL_ASSERT; - if (desc.depth_attachment) + if (desc.depth_stencil_state) { - glEnable(GL_DEPTH_TEST); - GLenum depth_func = map_compare_func(desc.depth_attachment->func); - SRB2_ASSERT(depth_func != GL_ZERO); - glDepthFunc(depth_func); - glDepthMask(desc.depth_attachment->write ? GL_TRUE : GL_FALSE); + if (desc.depth_stencil_state->depth_test) + { + gl_->Enable(GL_DEPTH_TEST); + GL_ASSERT; + GLenum depth_func = map_compare_func(desc.depth_stencil_state->depth_func); + SRB2_ASSERT(depth_func != GL_ZERO); + gl_->DepthFunc(depth_func); + GL_ASSERT; + gl_->DepthMask(desc.depth_stencil_state->depth_write ? GL_TRUE : GL_FALSE); + GL_ASSERT; + } + else + { + gl_->Disable(GL_DEPTH_TEST); + GL_ASSERT; + } + + if (desc.depth_stencil_state->depth_write) + { + gl_->DepthMask(GL_TRUE); + GL_ASSERT; + } + else + { + gl_->DepthMask(GL_FALSE); + GL_ASSERT; + } + + if (desc.depth_stencil_state->stencil_test) + { + gl_->Enable(GL_STENCIL_TEST); + stencil_front_reference_ = 0; + stencil_back_reference_ = 0; + stencil_front_compare_mask_ = 0xFF; + stencil_back_compare_mask_ = 0xFF; + stencil_front_write_mask_ = 0xFF; + stencil_back_write_mask_ = 0xFF; + GL_ASSERT; + + gl_->StencilFuncSeparate( + GL_FRONT, + map_compare_func(desc.depth_stencil_state->front.stencil_compare), + stencil_front_reference_, + stencil_front_compare_mask_ + ); + GL_ASSERT; + gl_->StencilFuncSeparate( + GL_BACK, + map_compare_func(desc.depth_stencil_state->back.stencil_compare), + stencil_back_reference_, + stencil_back_compare_mask_ + ); + GL_ASSERT; + + gl_->StencilMaskSeparate(GL_FRONT, stencil_front_write_mask_); + GL_ASSERT; + gl_->StencilMaskSeparate(GL_BACK, stencil_back_write_mask_); + GL_ASSERT; + } + else + { + gl_->Disable(GL_STENCIL_TEST); + GL_ASSERT; + } } else { - glDisable(GL_DEPTH_TEST); + gl_->Disable(GL_DEPTH_TEST); + GL_ASSERT; + gl_->Disable(GL_STENCIL_TEST); + GL_ASSERT; + gl_->StencilMask(0); + GL_ASSERT; } - if (desc.color_attachment.blend) + if (desc.color_state.blend) { - rhi::BlendDesc& bl = *desc.color_attachment.blend; - glEnable(GL_BLEND); - glBlendFuncSeparate( + rhi::BlendDesc& bl = *desc.color_state.blend; + gl_->Enable(GL_BLEND); + GL_ASSERT; + gl_->BlendFuncSeparate( map_blend_factor(bl.source_factor_color), map_blend_factor(bl.dest_factor_color), map_blend_factor(bl.source_factor_alpha), map_blend_factor(bl.dest_factor_alpha) ); - glBlendEquationSeparate(map_blend_function(bl.color_function), map_blend_function(bl.alpha_function)); - glBlendColor(desc.blend_color.r, desc.blend_color.g, desc.blend_color.b, desc.blend_color.a); + GL_ASSERT; + gl_->BlendEquationSeparate(map_blend_function(bl.color_function), map_blend_function(bl.alpha_function)); + GL_ASSERT; + gl_->BlendColor(desc.blend_color.r, desc.blend_color.g, desc.blend_color.b, desc.blend_color.a); + GL_ASSERT; } else { - glDisable(GL_BLEND); + gl_->Disable(GL_BLEND); } - glColorMask( - desc.color_attachment.color_mask.r ? GL_TRUE : GL_FALSE, - desc.color_attachment.color_mask.g ? GL_TRUE : GL_FALSE, - desc.color_attachment.color_mask.b ? GL_TRUE : GL_FALSE, - desc.color_attachment.color_mask.a ? GL_TRUE : GL_FALSE + gl_->ColorMask( + desc.color_state.color_mask.r ? GL_TRUE : GL_FALSE, + desc.color_state.color_mask.g ? GL_TRUE : GL_FALSE, + desc.color_state.color_mask.b ? GL_TRUE : GL_FALSE, + desc.color_state.color_mask.a ? GL_TRUE : GL_FALSE ); + GL_ASSERT; GLenum cull_face = map_cull_mode(desc.cull); if (cull_face == GL_NONE) { - glDisable(GL_CULL_FACE); + gl_->Disable(GL_CULL_FACE); + GL_ASSERT; } else { - glEnable(GL_CULL_FACE); - glCullFace(cull_face); + gl_->Enable(GL_CULL_FACE); + GL_ASSERT; + gl_->CullFace(cull_face); + GL_ASSERT; } - glFrontFace(map_winding(desc.winding)); + gl_->FrontFace(map_winding(desc.winding)); + GL_ASSERT; current_pipeline_ = pipeline; current_primitive_type_ = desc.primitive; } -void Gles2Rhi::update_bindings(Handle<GraphicsContext> ctx, const UpdateBindingsInfo& info) +void Gl2Rhi::bind_uniform_set(Handle<GraphicsContext> ctx, uint32_t slot, Handle<UniformSet> set) { SRB2_ASSERT(graphics_context_active_ == true && graphics_context_generation_ == ctx.generation()); SRB2_ASSERT(current_render_pass_.has_value() == true && current_pipeline_.has_value() == true); SRB2_ASSERT(pipeline_slab_.is_valid(*current_pipeline_)); - auto& pl = *static_cast<Gles2Pipeline*>(&pipeline_slab_[*current_pipeline_]); + auto& pl = pipeline_slab_[*current_pipeline_]; - // TODO assert compatibility of binding data with pipeline - SRB2_ASSERT(info.vertex_buffers.size() == pl.desc.vertex_input.buffer_layouts.size()); - SRB2_ASSERT(info.sampler_textures.size() == pl.desc.sampler_input.enabled_samplers.size()); + SRB2_ASSERT(uniform_set_slab_.is_valid(set)); + auto& us = uniform_set_slab_[set]; + + auto& uniform_input = pl.desc.uniform_input; + SRB2_ASSERT(slot < uniform_input.enabled_uniforms.size()); + SRB2_ASSERT(us.uniforms.size() == uniform_input.enabled_uniforms[slot].size()); + + // Assert compatibility of uniform set with pipeline's set slot + for (size_t i = 0; i < us.uniforms.size(); i++) + { + SRB2_ASSERT( + rhi::uniform_format(uniform_input.enabled_uniforms[slot][i]) == rhi::uniform_variant_format(us.uniforms[i]) + ); + } + + // Apply uniforms + // TODO use Uniform Buffer Objects to optimize this. + // We don't really *need* to, though, probably... + // Also, we know that any given uniform name is uniquely present in a single uniform group asserted during pipeline + // compilation. This is an RHI requirement to support backends that don't have UBOs. + for (size_t i = 0; i < us.uniforms.size(); i++) + { + auto& uniform_name = uniform_input.enabled_uniforms[slot][i]; + auto& update_data = us.uniforms[i]; + SRB2_ASSERT(pl.uniform_locations.find(uniform_name) != pl.uniform_locations.end()); + GLuint pipeline_uniform = pl.uniform_locations[uniform_name]; + + auto visitor = srb2::Overload { + [&](const float& value) + { + gl_->Uniform1f(pipeline_uniform, value); + GL_ASSERT; + }, + [&](const glm::vec2& value) + { + gl_->Uniform2f(pipeline_uniform, value.x, value.y); + GL_ASSERT; + }, + [&](const glm::vec3& value) + { + gl_->Uniform3f(pipeline_uniform, value.x, value.y, value.z); + GL_ASSERT; + }, + [&](const glm::vec4& value) + { + gl_->Uniform4f(pipeline_uniform, value.x, value.y, value.z, value.w); + GL_ASSERT; + }, + [&](const int32_t& value) + { + gl_->Uniform1i(pipeline_uniform, value); + GL_ASSERT; + }, + [&](const glm::ivec2& value) + { + gl_->Uniform2i(pipeline_uniform, value.x, value.y); + GL_ASSERT; + }, + [&](const glm::ivec3& value) + { + gl_->Uniform3i(pipeline_uniform, value.x, value.y, value.z); + GL_ASSERT; + }, + [&](const glm::ivec4& value) + { + gl_->Uniform4i(pipeline_uniform, value.x, value.y, value.z, value.w); + GL_ASSERT; + }, + [&](const glm::mat2& value) + { + gl_->UniformMatrix2fv(pipeline_uniform, 1, false, glm::value_ptr(value)); + GL_ASSERT; + }, + [&](const glm::mat3& value) + { + gl_->UniformMatrix3fv(pipeline_uniform, 1, false, glm::value_ptr(value)); + GL_ASSERT; + }, + [&](const glm::mat4& value) + { + gl_->UniformMatrix4fv(pipeline_uniform, 1, false, glm::value_ptr(value)); + GL_ASSERT; + }, + }; + std::visit(visitor, update_data); + } +} + +void Gl2Rhi::bind_binding_set(Handle<GraphicsContext> ctx, Handle<BindingSet> set) +{ + SRB2_ASSERT(graphics_context_active_ == true && graphics_context_generation_ == ctx.generation()); + SRB2_ASSERT(current_render_pass_.has_value() == true && current_pipeline_.has_value() == true); + + SRB2_ASSERT(pipeline_slab_.is_valid(*current_pipeline_)); + auto& pl = pipeline_slab_[*current_pipeline_]; + + SRB2_ASSERT(binding_set_slab_.is_valid(set)); + auto& bs = binding_set_slab_[set]; + + SRB2_ASSERT(bs.textures.size() == pl.desc.sampler_input.enabled_samplers.size()); // TODO only disable the vertex attributes of the previously bound pipeline (performance) for (GLuint i = 0; i < kMaxVertexAttributes; i++) { - glDisableVertexAttribArray(i); + gl_->DisableVertexAttribArray(i); } // Update the vertex attributes with the new vertex buffer bindings. - // OpenGL ES does not require binding buffers to the pipeline the same way Vulkan does. + // OpenGL 2 does not require binding buffers to the pipeline the same way Vulkan does. // Instead, we need to find the pipeline vertex attributes which would be affected by // the changing set of vertex buffers, and reassign their Vertex Attribute Pointers. for (size_t i = 0; i < pl.desc.vertex_input.attr_layouts.size(); i++) @@ -1097,16 +1630,16 @@ void Gles2Rhi::update_bindings(Handle<GraphicsContext> ctx, const UpdateBindings GLint vertex_attr_size = map_vertex_attribute_format_size(vert_attr_format); SRB2_ASSERT(vertex_attr_size != 0); - rhi::Handle<rhi::Buffer> vertex_buffer_handle; - uint32_t vertex_buffer_offset; - std::tie(vertex_buffer_handle, vertex_buffer_offset) = info.vertex_buffers[attr_layout.buffer_index]; - SRB2_ASSERT(buffer_slab_.is_valid(vertex_buffer_handle) == true); - auto& buffer = *static_cast<Gles2Buffer*>(&buffer_slab_[vertex_buffer_handle]); + uint32_t vertex_buffer_offset = 0; + auto& vertex_binding = bs.vertex_buffer_bindings[attr_layout.buffer_index]; + rhi::Handle<rhi::Buffer> vertex_buffer_handle = vertex_binding.vertex_buffer; + SRB2_ASSERT(buffer_slab_.is_valid(vertex_binding.vertex_buffer) == true); + auto& buffer = *static_cast<Gl2Buffer*>(&buffer_slab_[vertex_buffer_handle]); SRB2_ASSERT(buffer.desc.type == rhi::BufferType::kVertexBuffer); - glBindBuffer(GL_ARRAY_BUFFER, buffer.buffer); - glEnableVertexAttribArray(gl_attr_location); - glVertexAttribPointer( + gl_->BindBuffer(GL_ARRAY_BUFFER, buffer.buffer); + gl_->EnableVertexAttribArray(gl_attr_location); + gl_->VertexAttribPointer( gl_attr_location, vertex_attr_size, vertex_attr_type, @@ -1116,32 +1649,16 @@ void Gles2Rhi::update_bindings(Handle<GraphicsContext> ctx, const UpdateBindings ); } - rhi::Handle<rhi::Buffer> index_buffer_handle; - std::tie(index_buffer_handle, index_buffer_offset_) = info.index_buffer; - if (index_buffer_handle == rhi::kNullHandle) - { - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - } - else - { - SRB2_ASSERT(buffer_slab_.is_valid(index_buffer_handle)); - auto& ib = *static_cast<Gles2Buffer*>(&buffer_slab_[index_buffer_handle]); - SRB2_ASSERT(ib.desc.type == rhi::BufferType::kIndexBuffer); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ib.buffer); - } + gl_->BindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - for (size_t i = 0; i < info.sampler_textures.size(); i++) + // Bind the samplers to the uniforms + for (auto& texture_binding : bs.textures) { - auto& sampler_name = pl.desc.sampler_input.enabled_samplers[i]; - rhi::Handle<rhi::Texture> texture_handle = info.sampler_textures[i]; - SRB2_ASSERT(texture_slab_.is_valid(texture_handle)); - auto& t = *static_cast<Gles2Texture*>(&texture_slab_[texture_handle]); - SRB2_ASSERT(pl.sampler_locations.find(sampler_name) != pl.sampler_locations.end()); + auto sampler_name = texture_binding.first; + GLuint texture_gl_name = texture_binding.second; GLuint sampler_uniform_loc = pl.sampler_locations[sampler_name]; - GLenum active_texture = GL_TEXTURE0; GLuint uniform_value = 0; - switch (sampler_name) { case rhi::SamplerName::kSampler0: @@ -1161,148 +1678,304 @@ void Gles2Rhi::update_bindings(Handle<GraphicsContext> ctx, const UpdateBindings uniform_value = 3; break; } - glActiveTexture(active_texture); - glBindTexture(GL_TEXTURE_2D, t.texture); - glUniform1i(sampler_uniform_loc, uniform_value); + gl_->ActiveTexture(active_texture); + GL_ASSERT; + gl_->BindTexture(GL_TEXTURE_2D, texture_gl_name); + GL_ASSERT; + gl_->Uniform1i(sampler_uniform_loc, uniform_value); + GL_ASSERT; } } -void Gles2Rhi::update_uniforms(Handle<GraphicsContext> ctx, tcb::span<UniformUpdateData> uniforms) +void Gl2Rhi::bind_index_buffer(Handle<GraphicsContext> ctx, Handle<Buffer> buffer) { SRB2_ASSERT(graphics_context_active_ == true && graphics_context_generation_ == ctx.generation()); SRB2_ASSERT(current_render_pass_.has_value() == true && current_pipeline_.has_value() == true); - SRB2_ASSERT(pipeline_slab_.is_valid(*current_pipeline_)); - auto& pl = *static_cast<Gles2Pipeline*>(&pipeline_slab_[*current_pipeline_]); - - // TODO assert compatibility of uniform data with pipeline - // The uniforms need to be the same size, names and value types as the pipeline. - // RHI doesn't support updating selectively; the whole set must be updated at once altogether. - SRB2_ASSERT(uniforms.size() == pl.desc.uniform_input.enabled_uniforms.size()); - - for (size_t i = 0; i < uniforms.size(); i++) - { - auto& update_data = uniforms[i]; + SRB2_ASSERT(buffer_slab_.is_valid(buffer)); + auto& ib = buffer_slab_[buffer]; - SRB2_ASSERT(pl.uniform_locations.find(update_data.name) != pl.uniform_locations.end()); - SRB2_ASSERT(pl.desc.uniform_input.enabled_uniforms[i] == update_data.name); - GLuint pipeline_uniform = pl.uniform_locations[update_data.name]; + SRB2_ASSERT(ib.desc.type == rhi::BufferType::kIndexBuffer); - struct UniformVariantVisitor - { - rhi::UniformName name; - GLuint uniform; + current_index_buffer_ = buffer; - void operator()(const float& value) const noexcept - { - SRB2_ASSERT(rhi::uniform_format(name) == rhi::UniformFormat::kFloat); - glUniform1f(uniform, value); - } - void operator()(const std::array<float, 2>& value) const noexcept - { - SRB2_ASSERT(rhi::uniform_format(name) == rhi::UniformFormat::kFloat2); - glUniform2f(uniform, value[0], value[1]); - } - void operator()(const std::array<float, 3>& value) const noexcept - { - SRB2_ASSERT(rhi::uniform_format(name) == rhi::UniformFormat::kFloat3); - glUniform3f(uniform, value[0], value[1], value[2]); - } - void operator()(const std::array<float, 4>& value) const noexcept - { - SRB2_ASSERT(rhi::uniform_format(name) == rhi::UniformFormat::kFloat4); - glUniform4f(uniform, value[0], value[1], value[2], value[3]); - } - void operator()(const int32_t& value) const noexcept - { - SRB2_ASSERT(rhi::uniform_format(name) == rhi::UniformFormat::kInt); - glUniform1i(uniform, value); - } - void operator()(const std::array<int32_t, 2>& value) const noexcept - { - SRB2_ASSERT(rhi::uniform_format(name) == rhi::UniformFormat::kInt2); - glUniform2i(uniform, value[0], value[1]); - } - void operator()(const std::array<int32_t, 3>& value) const noexcept - { - SRB2_ASSERT(rhi::uniform_format(name) == rhi::UniformFormat::kInt3); - glUniform3i(uniform, value[0], value[1], value[2]); - } - void operator()(const std::array<int32_t, 4>& value) const noexcept - { - SRB2_ASSERT(rhi::uniform_format(name) == rhi::UniformFormat::kInt4); - glUniform4i(uniform, value[0], value[1], value[2], value[3]); - } - void operator()(const std::array<std::array<float, 2>, 2>& value) const noexcept - { - SRB2_ASSERT(rhi::uniform_format(name) == rhi::UniformFormat::kMat2); - glUniformMatrix2fv(uniform, 1, false, reinterpret_cast<const GLfloat*>(&value)); - } - void operator()(const std::array<std::array<float, 3>, 3>& value) const noexcept - { - SRB2_ASSERT(rhi::uniform_format(name) == rhi::UniformFormat::kMat3); - glUniformMatrix3fv(uniform, 1, false, reinterpret_cast<const GLfloat*>(&value)); - } - void operator()(const std::array<std::array<float, 4>, 4>& value) const noexcept - { - SRB2_ASSERT(rhi::uniform_format(name) == rhi::UniformFormat::kMat4); - glUniformMatrix4fv(uniform, 1, false, reinterpret_cast<const GLfloat*>(&value)); - } - }; - std::visit(UniformVariantVisitor {update_data.name, pipeline_uniform}, update_data.value); - } + gl_->BindBuffer(GL_ELEMENT_ARRAY_BUFFER, ib.buffer); } -void Gles2Rhi::set_scissor(Handle<GraphicsContext> ctx, const Rect& rect) +void Gl2Rhi::set_scissor(Handle<GraphicsContext> ctx, const Rect& rect) { SRB2_ASSERT(graphics_context_active_ == true && graphics_context_generation_ == ctx.generation()); SRB2_ASSERT(current_render_pass_.has_value() == true && current_pipeline_.has_value() == true); - // TODO handle scissor pipeline state + + gl_->Enable(GL_SCISSOR_TEST); + gl_->Scissor(rect.x, rect.y, rect.w, rect.h); } -void Gles2Rhi::set_viewport(Handle<GraphicsContext> ctx, const Rect& rect) +void Gl2Rhi::set_viewport(Handle<GraphicsContext> ctx, const Rect& rect) { SRB2_ASSERT(graphics_context_active_ == true && graphics_context_generation_ == ctx.generation()); SRB2_ASSERT(current_render_pass_.has_value() == true && current_pipeline_.has_value() == true); - // TODO handle viewport pipeline state + + gl_->Viewport(rect.x, rect.y, rect.w, rect.h); + GL_ASSERT; } -void Gles2Rhi::draw(Handle<GraphicsContext> ctx, uint32_t vertex_count, uint32_t first_vertex) +void Gl2Rhi::draw(Handle<GraphicsContext> ctx, uint32_t vertex_count, uint32_t first_vertex) { SRB2_ASSERT(graphics_context_active_ == true && graphics_context_generation_ == ctx.generation()); SRB2_ASSERT(current_render_pass_.has_value() == true && current_pipeline_.has_value() == true); - glDrawArrays(map_primitive_mode(current_primitive_type_), first_vertex, vertex_count); + gl_->DrawArrays(map_primitive_mode(current_primitive_type_), first_vertex, vertex_count); + GL_ASSERT; } -void Gles2Rhi::draw_indexed( - Handle<GraphicsContext> ctx, - uint32_t index_count, - uint32_t first_index, - uint32_t vertex_offset -) +void Gl2Rhi::draw_indexed(Handle<GraphicsContext> ctx, uint32_t index_count, uint32_t first_index) { SRB2_ASSERT(graphics_context_active_ == true && graphics_context_generation_ == ctx.generation()); - glDrawElements( + + SRB2_ASSERT(current_index_buffer_ != kNullHandle); +#ifndef NDEBUG + { + auto& ib = buffer_slab_[current_index_buffer_]; + SRB2_ASSERT((index_count + first_index) * 2 + index_buffer_offset_ <= ib.desc.size); + } +#endif + + gl_->DrawElements( map_primitive_mode(current_primitive_type_), - first_index, + index_count, GL_UNSIGNED_SHORT, - reinterpret_cast<const void*>(index_buffer_offset_) + (const void*)((size_t)first_index * 2 + index_buffer_offset_) ); + GL_ASSERT; +} + +void Gl2Rhi::read_pixels(Handle<GraphicsContext> ctx, const Rect& rect, PixelFormat format, tcb::span<std::byte> out) +{ + SRB2_ASSERT(graphics_context_active_ == true && graphics_context_generation_ == ctx.generation()); + SRB2_ASSERT(current_render_pass_.has_value()); + + std::tuple<GLenum, GLenum, GLuint> gl_format = map_pixel_data_format(format); + GLenum layout = std::get<0>(gl_format); + GLenum type = std::get<1>(gl_format); + GLint size = std::get<2>(gl_format); + + // Pack alignment comes into play. + uint32_t pack_stride = (rect.w * size + (kPixelRowPackAlignment - 1)) & ~(kPixelRowPackAlignment - 1); + + SRB2_ASSERT(out.size_bytes() == pack_stride * rect.h); + + bool is_back; + Rect src_dim; + auto render_pass_visitor = srb2::Overload { + [&](const DefaultRenderPassState& state) { + is_back = true; + src_dim = platform_->get_default_framebuffer_dimensions(); + }, + [&](const RenderPassBeginInfo& state) { + is_back = false; + SRB2_ASSERT(texture_slab_.is_valid(state.color_attachment)); + auto& attach_tex = texture_slab_[state.color_attachment]; + src_dim = {0, 0, attach_tex.desc.width, attach_tex.desc.height}; + } + }; + std::visit(render_pass_visitor, *current_render_pass_); + + SRB2_ASSERT(rect.x >= 0); + SRB2_ASSERT(rect.y >= 0); + SRB2_ASSERT(rect.x + rect.w <= src_dim.w); + SRB2_ASSERT(rect.y + rect.h <= src_dim.h); +#ifdef HAVE_GLES3 + GLenum read_buffer = is_back ? GL_BACK_LEFT : GL_COLOR_ATTACHMENT0; + gl_->ReadBuffer(read_buffer); + GL_ASSERT; +#endif + + gl_->ReadPixels(rect.x, rect.y, rect.w, rect.h, layout, type, out.data()); + GL_ASSERT; +} + +void Gl2Rhi::set_stencil_reference(Handle<GraphicsContext> ctx, CullMode face, uint8_t reference) +{ + SRB2_ASSERT(face != CullMode::kNone); + SRB2_ASSERT(graphics_context_active_ == true && graphics_context_generation_ == ctx.generation()); + SRB2_ASSERT(current_render_pass_.has_value()); + SRB2_ASSERT(current_pipeline_.has_value()); + + auto& pl = pipeline_slab_[*current_pipeline_]; + + if (face == CullMode::kFront) + { + stencil_front_reference_ = reference; + gl_->StencilFuncSeparate( + GL_FRONT, + map_compare_func(pl.desc.depth_stencil_state->front.stencil_compare), + stencil_front_reference_, + stencil_front_compare_mask_ + ); + } + else if (face == CullMode::kBack) + { + stencil_back_reference_ = reference; + gl_->StencilFuncSeparate( + GL_BACK, + map_compare_func(pl.desc.depth_stencil_state->back.stencil_compare), + stencil_back_reference_, + stencil_back_compare_mask_ + ); + } } -void Gles2Rhi::finish() +void Gl2Rhi::set_stencil_compare_mask(Handle<GraphicsContext> ctx, CullMode face, uint8_t compare_mask) +{ + SRB2_ASSERT(face != CullMode::kNone); + SRB2_ASSERT(graphics_context_active_ == true && graphics_context_generation_ == ctx.generation()); + SRB2_ASSERT(current_render_pass_.has_value()); + SRB2_ASSERT(current_pipeline_.has_value()); + + auto& pl = pipeline_slab_[*current_pipeline_]; + + if (face == CullMode::kFront) + { + stencil_front_compare_mask_ = compare_mask; + gl_->StencilFuncSeparate( + GL_FRONT, + map_compare_func(pl.desc.depth_stencil_state->front.stencil_compare), + stencil_front_reference_, + stencil_front_compare_mask_ + ); + } + else if (face == CullMode::kBack) + { + stencil_back_compare_mask_ = compare_mask; + gl_->StencilFuncSeparate( + GL_BACK, + map_compare_func(pl.desc.depth_stencil_state->back.stencil_compare), + stencil_back_reference_, + stencil_back_compare_mask_ + ); + } +} + +void Gl2Rhi::set_stencil_write_mask(Handle<GraphicsContext> ctx, CullMode face, uint8_t write_mask) +{ + SRB2_ASSERT(face != CullMode::kNone); + SRB2_ASSERT(graphics_context_active_ == true && graphics_context_generation_ == ctx.generation()); + SRB2_ASSERT(current_render_pass_.has_value()); + SRB2_ASSERT(current_pipeline_.has_value()); + + if (face == CullMode::kFront) + { + stencil_front_write_mask_ = write_mask; + gl_->StencilMaskSeparate(GL_FRONT, stencil_front_write_mask_); + } + else if (face == CullMode::kBack) + { + stencil_back_write_mask_ = write_mask; + gl_->StencilMaskSeparate(GL_BACK, stencil_back_write_mask_); + } +} + +TextureDetails Gl2Rhi::get_texture_details(Handle<Texture> texture) +{ + SRB2_ASSERT(texture_slab_.is_valid(texture)); + auto& t = texture_slab_[texture]; + + TextureDetails ret {}; + ret.format = t.desc.format; + ret.width = t.desc.width; + ret.height = t.desc.height; + + return ret; +} + +Rect Gl2Rhi::get_renderbuffer_size(Handle<Renderbuffer> renderbuffer) +{ + SRB2_ASSERT(renderbuffer_slab_.is_valid(renderbuffer)); + auto& rb = renderbuffer_slab_[renderbuffer]; + + Rect ret {}; + ret.x = 0; + ret.y = 0; + ret.w = rb.desc.width; + ret.h = rb.desc.height; + + return ret; +} + +uint32_t Gl2Rhi::get_buffer_size(Handle<Buffer> buffer) +{ + SRB2_ASSERT(buffer_slab_.is_valid(buffer)); + auto& buf = buffer_slab_[buffer]; + + return buf.desc.size; +} + +void Gl2Rhi::finish() { SRB2_ASSERT(graphics_context_active_ == false); - for (auto it = disposal_.begin(); it != disposal_.end(); it++) + binding_set_slab_.clear(); + uniform_set_slab_.clear(); + + // I sure hope creating FBOs isn't costly on the driver! + for (auto& fbset : framebuffers_) { - (*it)(); + gl_->DeleteFramebuffers(1, (GLuint*)&fbset.second); + GL_ASSERT; } - disposal_.clear(); + framebuffers_.clear(); } -void rhi::load_gles2(Gles2LoadFunc func) +void Gl2Rhi::copy_framebuffer_to_texture( + Handle<GraphicsContext> ctx, + Handle<Texture> dst_tex, + const Rect& dst_region, + const Rect& src_region +) { - gladLoadGLES2(static_cast<GLADloadfunc>(func)); + SRB2_ASSERT(graphics_context_active_ == true); + SRB2_ASSERT(current_render_pass_.has_value()); + SRB2_ASSERT(texture_slab_.is_valid(dst_tex)); + + auto& tex = texture_slab_[dst_tex]; + SRB2_ASSERT(dst_region.w == src_region.w); + SRB2_ASSERT(dst_region.h == src_region.h); + SRB2_ASSERT(dst_region.x >= 0); + SRB2_ASSERT(dst_region.y >= 0); + SRB2_ASSERT(dst_region.x + dst_region.w <= tex.desc.width); + SRB2_ASSERT(dst_region.y + dst_region.h <= tex.desc.height); + + bool is_back; + Rect src_dim; + auto render_pass_visitor = srb2::Overload { + [&](const DefaultRenderPassState& state) { + is_back = true; + src_dim = platform_->get_default_framebuffer_dimensions(); + }, + [&](const RenderPassBeginInfo& state) { + is_back = false; + SRB2_ASSERT(texture_slab_.is_valid(state.color_attachment)); + auto& attach_tex = texture_slab_[state.color_attachment]; + src_dim = {0, 0, attach_tex.desc.width, attach_tex.desc.height}; + } + }; + std::visit(render_pass_visitor, *current_render_pass_); + + SRB2_ASSERT(src_region.x >= 0); + SRB2_ASSERT(src_region.y >= 0); + SRB2_ASSERT(src_region.x + src_region.w <= src_dim.w); + SRB2_ASSERT(src_region.y + src_region.h <= src_dim.h); + +#ifdef HAVE_GLES3 + GLenum read_buffer = is_back ? GL_BACK_LEFT : GL_COLOR_ATTACHMENT0; + gl_->ReadBuffer(read_buffer); + GL_ASSERT; +#endif + + + gl_->BindTexture(GL_TEXTURE_2D, tex.texture); + GL_ASSERT; + gl_->CopyTexSubImage2D(GL_TEXTURE_2D, 0, dst_region.x, dst_region.y, src_region.x, src_region.y, dst_region.w, dst_region.h); + GL_ASSERT; } diff --git a/src/rhi/gles2/gles2_rhi.hpp b/src/rhi/gles2/gles2_rhi.hpp index d9e7a63cbe4055c60b483a806460338cdbefedcb..d2e19712b0e6b0b190f6bb0ec129d132bec31b74 100644 --- a/src/rhi/gles2/gles2_rhi.hpp +++ b/src/rhi/gles2/gles2_rhi.hpp @@ -8,8 +8,8 @@ // See the 'LICENSE' file for more details. //----------------------------------------------------------------------------- -#ifndef __SRB2_RHI_GLES2_RHI_HPP__ -#define __SRB2_RHI_GLES2_RHI_HPP__ +#ifndef __SRB2_RHI_GL2_RHI_HPP__ +#define __SRB2_RHI_GL2_RHI_HPP__ #include <functional> #include <memory> @@ -24,71 +24,129 @@ namespace srb2::rhi { -struct Gles2FramebufferKey +struct Gl2FramebufferKey { - TextureOrRenderbuffer color; - std::optional<TextureOrRenderbuffer> depth; + Handle<Texture> color; + std::optional<Handle<Renderbuffer>> depth_stencil; - bool operator==(const Gles2FramebufferKey& rhs) const noexcept { return color == rhs.color && depth == rhs.depth; } + bool operator==(const Gl2FramebufferKey& rhs) const noexcept + { + return color == rhs.color && depth_stencil == rhs.depth_stencil; + } - bool operator!=(const Gles2FramebufferKey& rhs) const noexcept { return !(*this == rhs); } + bool operator!=(const Gl2FramebufferKey& rhs) const noexcept { return !(*this == rhs); } }; } // namespace srb2::rhi -// To make sure the compiler selects the struct specialization of std::hash for Gles2FramebufferKey, +// To make sure the compiler selects the struct specialization of std::hash for Gl2FramebufferKey, // we need to split the namespace declarations _before_ the instantiation of std::unordered_map. template <> -struct std::hash<srb2::rhi::Gles2FramebufferKey> +struct std::hash<srb2::rhi::Gl2FramebufferKey> { - std::size_t operator()(const srb2::rhi::Gles2FramebufferKey& key) const + std::size_t operator()(const srb2::rhi::Gl2FramebufferKey& key) const { - struct GetHandleHashVisitor + std::size_t color_hash = std::hash<srb2::rhi::Handle<srb2::rhi::Texture>>()(key.color); + std::size_t depth_stencil_hash = 0; + if (key.depth_stencil) { - uint32_t operator()(const srb2::rhi::Handle<srb2::rhi::Texture>& handle) const noexcept - { - return std::hash<srb2::rhi::Handle<srb2::rhi::Texture>>()(handle); - } - uint32_t operator()(const srb2::rhi::Handle<srb2::rhi::Renderbuffer>& handle) const noexcept - { - return std::hash<srb2::rhi::Handle<srb2::rhi::Renderbuffer>>()(handle); - } - }; - std::size_t color_hash = std::visit(GetHandleHashVisitor {}, key.color); - std::size_t depth_hash = 0; - if (key.depth) - { - depth_hash = std::visit(GetHandleHashVisitor {}, *key.depth); + depth_stencil_hash = std::hash<srb2::rhi::Handle<srb2::rhi::Renderbuffer>>()(*key.depth_stencil); } - return color_hash ^ (depth_hash << 1); + return color_hash ^ (depth_stencil_hash << 1); } }; +struct GladGLES2Context; + namespace srb2::rhi { -/// @brief Platform-specific implementation details for the GLES2 backend. -struct Gles2Platform +typedef void (*GlProc)(void); +typedef GlProc (*GlLoadFunc)(const char* name); + +/// @brief Platform-specific implementation details for the GL2 backend. +struct Gl2Platform { - virtual ~Gles2Platform(); + virtual ~Gl2Platform(); virtual void present() = 0; - virtual std::tuple<std::string, std::string> find_shader_sources(PipelineProgram program) = 0; + virtual std::tuple<std::vector<std::string>, std::vector<std::string>> find_shader_sources(PipelineProgram program) = 0; virtual Rect get_default_framebuffer_dimensions() = 0; }; -class Gles2Rhi final : public Rhi +struct Gl2Texture : public rhi::Texture { - std::unique_ptr<Gles2Platform> platform_; + uint32_t texture; + rhi::TextureDesc desc; +}; - Slab<RenderPass> render_pass_slab_; - Slab<Texture> texture_slab_; - Slab<Buffer> buffer_slab_; - Slab<Renderbuffer> renderbuffer_slab_; - Slab<Pipeline> pipeline_slab_; +struct Gl2Buffer : public rhi::Buffer +{ + uint32_t buffer; + rhi::BufferDesc desc; +}; + +struct Gl2RenderPass : public rhi::RenderPass +{ + rhi::RenderPassDesc desc; +}; - std::unordered_map<Gles2FramebufferKey, uint32_t> framebuffers_ {16}; +struct Gl2Renderbuffer : public rhi::Renderbuffer +{ + uint32_t renderbuffer; + rhi::RenderbufferDesc desc; +}; + +struct Gl2UniformSet : public rhi::UniformSet +{ + std::vector<rhi::UniformVariant> uniforms; +}; + +struct Gl2BindingSet : public rhi::BindingSet +{ + std::vector<rhi::VertexAttributeBufferBinding> vertex_buffer_bindings; + std::unordered_map<rhi::SamplerName, uint32_t> textures {4}; +}; + +struct Gl2Pipeline : public rhi::Pipeline +{ + uint32_t vertex_shader = 0; + uint32_t fragment_shader = 0; + uint32_t program = 0; + std::unordered_map<rhi::VertexAttributeName, uint32_t> attrib_locations {2}; + std::unordered_map<rhi::UniformName, uint32_t> uniform_locations {2}; + std::unordered_map<rhi::SamplerName, uint32_t> sampler_locations {2}; + rhi::PipelineDesc desc; +}; + +struct Gl2GraphicsContext : public rhi::GraphicsContext +{ +}; + +struct Gl2ActiveUniform +{ + uint32_t type; + uint32_t location; +}; + +class Gl2Rhi final : public Rhi +{ + std::unique_ptr<Gl2Platform> platform_; + + std::unique_ptr<GladGLES2Context> gl_; + + Slab<Gl2RenderPass> render_pass_slab_; + Slab<Gl2Texture> texture_slab_; + Slab<Gl2Buffer> buffer_slab_; + Slab<Gl2Renderbuffer> renderbuffer_slab_; + Slab<Gl2Pipeline> pipeline_slab_; + Slab<Gl2UniformSet> uniform_set_slab_; + Slab<Gl2BindingSet> binding_set_slab_; + + Handle<Buffer> current_index_buffer_; + + std::unordered_map<Gl2FramebufferKey, uint32_t> framebuffers_ {16}; struct DefaultRenderPassState { @@ -98,64 +156,95 @@ class Gles2Rhi final : public Rhi std::optional<Handle<Pipeline>> current_pipeline_; PrimitiveType current_primitive_type_ = PrimitiveType::kPoints; bool graphics_context_active_ = false; - uint32_t graphics_context_generation_ = 0; + uint32_t graphics_context_generation_ = 1; uint32_t index_buffer_offset_ = 0; - std::vector<std::function<void()>> disposal_; + uint8_t stencil_front_reference_ = 0; + uint8_t stencil_front_compare_mask_ = 0xFF; + uint8_t stencil_front_write_mask_ = 0xFF; + uint8_t stencil_back_reference_ = 0; + uint8_t stencil_back_compare_mask_ = 0xFF; + uint8_t stencil_back_write_mask_ = 0xFF; public: - Gles2Rhi(std::unique_ptr<Gles2Platform>&& platform); - virtual ~Gles2Rhi(); + Gl2Rhi(std::unique_ptr<Gl2Platform>&& platform, GlLoadFunc load_func); + virtual ~Gl2Rhi(); virtual Handle<RenderPass> create_render_pass(const RenderPassDesc& desc) override; - virtual void destroy_render_pass(Handle<RenderPass>&& handle) override; - virtual Handle<Texture> - create_texture(const TextureDesc& desc, srb2::rhi::PixelFormat data_format, tcb::span<const std::byte> data) - override; - virtual void destroy_texture(Handle<Texture>&& handle) override; - virtual Handle<Buffer> create_buffer(const BufferDesc& desc, tcb::span<const std::byte> data) override; - virtual void destroy_buffer(Handle<Buffer>&& handle) override; - virtual Handle<Renderbuffer> create_renderbuffer(const RenderbufferDesc& desc) override; - virtual void destroy_renderbuffer(Handle<Renderbuffer>&& handle) override; + virtual void destroy_render_pass(Handle<RenderPass> handle) override; virtual Handle<Pipeline> create_pipeline(const PipelineDesc& desc) override; - virtual void destroy_pipeline(Handle<Pipeline>&& handle) override; + virtual void destroy_pipeline(Handle<Pipeline> handle) override; - virtual void - update_buffer_contents(Handle<Buffer> buffer, uint32_t offset, tcb::span<const std::byte> data) override; + virtual Handle<Texture> create_texture(const TextureDesc& desc) override; + virtual void destroy_texture(Handle<Texture> handle) override; + virtual Handle<Buffer> create_buffer(const BufferDesc& desc) override; + virtual void destroy_buffer(Handle<Buffer> handle) override; + virtual Handle<Renderbuffer> create_renderbuffer(const RenderbufferDesc& desc) override; + virtual void destroy_renderbuffer(Handle<Renderbuffer> handle) override; + + virtual TextureDetails get_texture_details(Handle<Texture> texture) override; + virtual Rect get_renderbuffer_size(Handle<Renderbuffer> renderbuffer) override; + virtual uint32_t get_buffer_size(Handle<Buffer> buffer) override; + + virtual void update_buffer( + Handle<GraphicsContext> ctx, + Handle<Buffer> buffer, + uint32_t offset, + tcb::span<const std::byte> data + ) override; virtual void update_texture( + Handle<GraphicsContext> ctx, Handle<Texture> texture, Rect region, srb2::rhi::PixelFormat data_format, tcb::span<const std::byte> data ) override; + virtual void update_texture_settings( + Handle<GraphicsContext> ctx, + Handle<Texture> texture, + TextureWrapMode u_wrap, + TextureWrapMode v_wrap, + TextureFilterMode min, + TextureFilterMode mag + ) override; + virtual Handle<UniformSet> + create_uniform_set(Handle<GraphicsContext> ctx, const CreateUniformSetInfo& info) override; + virtual Handle<BindingSet> + create_binding_set(Handle<GraphicsContext> ctx, Handle<Pipeline> pipeline, const CreateBindingSetInfo& info) + override; virtual Handle<GraphicsContext> begin_graphics() override; - virtual void end_graphics(Handle<GraphicsContext>&& ctx) override; + virtual void end_graphics(Handle<GraphicsContext> ctx) override; // Graphics context functions - virtual void begin_default_render_pass(Handle<GraphicsContext> ctx) override; + virtual void begin_default_render_pass(Handle<GraphicsContext> ctx, bool clear) override; virtual void begin_render_pass(Handle<GraphicsContext> ctx, const RenderPassBeginInfo& info) override; virtual void end_render_pass(Handle<GraphicsContext> ctx) override; virtual void bind_pipeline(Handle<GraphicsContext> ctx, Handle<Pipeline> pipeline) override; - virtual void update_bindings(Handle<GraphicsContext> ctx, const UpdateBindingsInfo& info) override; - virtual void update_uniforms(Handle<GraphicsContext> ctx, tcb::span<UniformUpdateData> uniforms) override; + virtual void bind_uniform_set(Handle<GraphicsContext> ctx, uint32_t slot, Handle<UniformSet> set) override; + virtual void bind_binding_set(Handle<GraphicsContext> ctx, Handle<BindingSet> set) override; + virtual void bind_index_buffer(Handle<GraphicsContext> ctx, Handle<Buffer> buffer) override; virtual void set_scissor(Handle<GraphicsContext> ctx, const Rect& rect) override; virtual void set_viewport(Handle<GraphicsContext> ctx, const Rect& rect) override; virtual void draw(Handle<GraphicsContext> ctx, uint32_t vertex_count, uint32_t first_vertex) override; + virtual void draw_indexed(Handle<GraphicsContext> ctx, uint32_t index_count, uint32_t first_index) override; virtual void - draw_indexed(Handle<GraphicsContext> ctx, uint32_t index_count, uint32_t first_index, uint32_t vertex_offset) - override; + read_pixels(Handle<GraphicsContext> ctx, const Rect& rect, PixelFormat format, tcb::span<std::byte> out) override; + virtual void copy_framebuffer_to_texture( + Handle<GraphicsContext> ctx, + Handle<Texture> dst_tex, + const Rect& dst_region, + const Rect& src_region + ) override; + virtual void set_stencil_reference(Handle<GraphicsContext> ctx, CullMode face, uint8_t reference) override; + virtual void set_stencil_compare_mask(Handle<GraphicsContext> ctx, CullMode face, uint8_t mask) override; + virtual void set_stencil_write_mask(Handle<GraphicsContext> ctx, CullMode face, uint8_t mask) override; virtual void present() override; virtual void finish() override; }; -typedef void (*Gles2Proc)(void); -typedef Gles2Proc (*Gles2LoadFunc)(const char* name); - -void load_gles2(Gles2LoadFunc func); - } // namespace srb2::rhi -#endif // __SRB2_RHI_GLES2_RHI_HPP__ +#endif // __SRB2_RHI_GL2_RHI_HPP__ diff --git a/thirdparty/glad/include/glad/gles2.h b/thirdparty/glad/include/glad/gles2.h index a03d1e26152b882cf91f53c3c5210de37ba7819f..9f6aa46e0f095b89434b4774cd8be5a0b75543cc 100644 --- a/thirdparty/glad/include/glad/gles2.h +++ b/thirdparty/glad/include/glad/gles2.h @@ -227,6 +227,13 @@ typedef void (*GLADpostcallback)(void *ret, const char *name, GLADapiproc apipro #define GL_DECR_WRAP 0x8508 #define GL_DELETE_STATUS 0x8B80 #define GL_DEPTH_ATTACHMENT 0x8D00 +#define GL_DEPTH_STENCIL_OES 0x84F9 +#define GL_UNSIGNED_INT_24_8_OES 0x84FA +#define GL_DEPTH24_STENCIL8_OES 0x88F0 +#ifndef GL_OES_packed_depth_stencil +#define GL_OES_packed_depth_stencil 1 +GLAPI int GLAD_GL_OES_packed_depth_stencil; +#endif #define GL_DEPTH_BITS 0x0D56 #define GL_DEPTH_BUFFER_BIT 0x00000100 #define GL_DEPTH_CLEAR_VALUE 0x0B73 @@ -268,6 +275,9 @@ typedef void (*GLADpostcallback)(void *ret, const char *name, GLADapiproc apipro #define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT 0x8CD7 #define GL_FRAMEBUFFER_UNSUPPORTED 0x8CDD #define GL_FRONT 0x0404 +#define GL_BACK 0x0405 +#define GL_BACK_LEFT 0x0402 +#define GL_BACK_RIGHT 0x0403 #define GL_FRONT_AND_BACK 0x0408 #define GL_FRONT_FACE 0x0B46 #define GL_FUNC_ADD 0x8006 @@ -644,6 +654,7 @@ typedef void (GLAD_API_PTR *PFNGLLINKPROGRAMPROC)(GLuint program); typedef void (GLAD_API_PTR *PFNGLPIXELSTOREIPROC)(GLenum pname, GLint param); typedef void (GLAD_API_PTR *PFNGLPOLYGONOFFSETPROC)(GLfloat factor, GLfloat units); typedef void (GLAD_API_PTR *PFNGLREADPIXELSPROC)(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void * pixels); +typedef void (GLAD_API_PTR *PFNGLREADBUFFERPROC)(GLenum src); typedef void (GLAD_API_PTR *PFNGLRELEASESHADERCOMPILERPROC)(void); typedef void (GLAD_API_PTR *PFNGLRENDERBUFFERSTORAGEPROC)(GLenum target, GLenum internalformat, GLsizei width, GLsizei height); typedef void (GLAD_API_PTR *PFNGLSAMPLECOVERAGEPROC)(GLfloat value, GLboolean invert); @@ -793,6 +804,8 @@ typedef struct GladGLES2Context { PFNGLPIXELSTOREIPROC PixelStorei; PFNGLPOLYGONOFFSETPROC PolygonOffset; PFNGLREADPIXELSPROC ReadPixels; + PFNGLREADBUFFERPROC ReadBuffer; + PFNGLRELEASESHADERCOMPILERPROC ReleaseShaderCompiler; PFNGLRENDERBUFFERSTORAGEPROC RenderbufferStorage; PFNGLSAMPLECOVERAGEPROC SampleCoverage; diff --git a/thirdparty/glad/src/gles2.c b/thirdparty/glad/src/gles2.c index 5157bf2881100e2c69b8fbdc3df0caa6a6585f79..63313e9096eb18a3f3b92d917cfd9cfb7786ff18 100644 --- a/thirdparty/glad/src/gles2.c +++ b/thirdparty/glad/src/gles2.c @@ -123,6 +123,7 @@ static void glad_gl_load_GL_ES_VERSION_2_0(GladGLES2Context *context, GLADuserpt context->PixelStorei = (PFNGLPIXELSTOREIPROC) load(userptr, "glPixelStorei"); context->PolygonOffset = (PFNGLPOLYGONOFFSETPROC) load(userptr, "glPolygonOffset"); context->ReadPixels = (PFNGLREADPIXELSPROC) load(userptr, "glReadPixels"); + context->ReadBuffer = (PFNGLREADBUFFERPROC) load(userptr, "glReadBuffer"); context->ReleaseShaderCompiler = (PFNGLRELEASESHADERCOMPILERPROC) load(userptr, "glReleaseShaderCompiler"); context->RenderbufferStorage = (PFNGLRENDERBUFFERSTORAGEPROC) load(userptr, "glRenderbufferStorage"); context->SampleCoverage = (PFNGLSAMPLECOVERAGEPROC) load(userptr, "glSampleCoverage"); @@ -343,7 +344,7 @@ int gladLoadGLES2Context(GladGLES2Context *context, GLADloadfunc load) { - + #ifdef __cplusplus