diff --git a/src/common/gl/texture.cpp b/src/common/gl/texture.cpp index d21f016b3..461fee141 100644 --- a/src/common/gl/texture.cpp +++ b/src/common/gl/texture.cpp @@ -1,10 +1,15 @@ #include "texture.h" #include "../assert.h" #include "../log.h" +#include Log_SetChannel(GL); namespace GL { +static constexpr u32 MAX_DIMENSIONS = std::numeric_limits::max(); +static constexpr u8 MAX_LEVELS = std::numeric_limits::max(); +static constexpr u8 MAX_SAMPLES = std::numeric_limits::max(); + Texture::Texture() = default; Texture::Texture(Texture&& moved) @@ -33,12 +38,33 @@ bool Texture::UseTextureStorage() const return UseTextureStorage(IsMultisampled()); } -bool Texture::Create(u32 width, u32 height, u32 samples, GLenum internal_format, GLenum format, GLenum type, - const void* data, bool linear_filter, bool wrap) +bool Texture::Create(u32 width, u32 height, u32 layers, u32 levels, u32 samples, GLenum internal_format, GLenum format, + GLenum type, const void* data /* = nullptr */, bool linear_filter /* = false */, + bool wrap /* = false */) { glGetError(); - const GLenum target = (samples > 1) ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D; + if (width > MAX_DIMENSIONS || height > MAX_DIMENSIONS || layers > MAX_DIMENSIONS || levels > MAX_DIMENSIONS || + samples > MAX_SAMPLES) + { + Log_ErrorPrintf("Invalid dimensions: %ux%ux%u %u %u", width, height, layers, levels, samples); + return false; + } + + if (samples > 1 && levels > 1) + { + Log_ErrorPrintf("Multisampled textures can't have mip levels"); + return false; + } + + if (layers > 1 && data) + { + Log_ErrorPrintf("Loading texture array data not currently supported"); + return false; + } + + const GLenum target = ((samples > 1) ? ((layers > 1) ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D_MULTISAMPLE_ARRAY) : + ((layers > 1) ? GL_TEXTURE_2D_ARRAY : GL_TEXTURE_2D)); GLuint id; glGenTextures(1, &id); @@ -48,27 +74,51 @@ bool Texture::Create(u32 width, u32 height, u32 samples, GLenum internal_format, { Assert(!data); if (UseTextureStorage(true)) - glTexStorage2DMultisample(target, samples, internal_format, width, height, GL_FALSE); + { + if (layers > 1) + glTexStorage3DMultisample(target, samples, internal_format, width, height, layers, GL_FALSE); + else + glTexStorage2DMultisample(target, samples, internal_format, width, height, GL_FALSE); + } else - glTexImage2DMultisample(target, samples, internal_format, width, height, GL_FALSE); + { + if (layers > 1) + glTexImage3DMultisample(target, samples, internal_format, width, height, layers, GL_FALSE); + else + glTexImage2DMultisample(target, samples, internal_format, width, height, GL_FALSE); + } } else { if (UseTextureStorage(false)) { - glTexStorage2D(target, 1, internal_format, width, height); - if (data) - glTexSubImage2D(target, 0, 0, 0, width, height, format, type, data); + if (layers > 1) + glTexStorage3D(target, levels, internal_format, width, height, layers); + else + glTexStorage2D(target, levels, internal_format, width, height); } else { - glTexImage2D(target, 0, internal_format, width, height, 0, format, type, data); + for (u32 i = 0; i < levels; i++) + { + // TODO: Fix data pointer here. + if (layers > 1) + glTexImage3D(target, i, internal_format, width, height, layers, 0, format, type, data); + else + glTexImage2D(target, i, internal_format, width, height, 0, format, type, data); + } + + glTexParameteri(target, GL_TEXTURE_BASE_LEVEL, 0); + glTexParameteri(target, GL_TEXTURE_MAX_LEVEL, levels); } glTexParameteri(target, GL_TEXTURE_MIN_FILTER, linear_filter ? GL_LINEAR : GL_NEAREST); glTexParameteri(target, GL_TEXTURE_MAG_FILTER, linear_filter ? GL_LINEAR : GL_NEAREST); glTexParameteri(target, GL_TEXTURE_WRAP_S, wrap ? GL_REPEAT : GL_CLAMP_TO_EDGE); glTexParameteri(target, GL_TEXTURE_WRAP_T, wrap ? GL_REPEAT : GL_CLAMP_TO_EDGE); + + if (layers > 1) + glTexParameteri(target, GL_TEXTURE_WRAP_R, wrap ? GL_REPEAT : GL_CLAMP_TO_EDGE); } // This doesn't exist on GLES2. @@ -87,21 +137,67 @@ bool Texture::Create(u32 width, u32 height, u32 samples, GLenum internal_format, Destroy(); m_id = id; - m_width = width; - m_height = height; - m_samples = samples; + m_width = static_cast(width); + m_height = static_cast(height); + m_layers = static_cast(layers); + m_levels = static_cast(levels); + m_samples = static_cast(samples); return true; } void Texture::Replace(u32 width, u32 height, GLenum internal_format, GLenum format, GLenum type, const void* data) { - Assert(IsValid() && m_samples == 1); + Assert(IsValid() && width < MAX_DIMENSIONS && height < MAX_DIMENSIONS && m_layers == 1 && m_samples == 1 && + m_levels == 1); - m_width = width; - m_height = height; + const bool size_changed = (width != m_width || height != m_height); - glBindTexture(GL_TEXTURE_2D, m_id); - glTexImage2D(GL_TEXTURE_2D, 0, internal_format, width, height, 0, format, type, data); + m_width = static_cast(width); + m_height = static_cast(height); + m_levels = 1; + + const GLenum target = GetGLTarget(); + glBindTexture(target, m_id); + + if (UseTextureStorage()) + { + if (size_changed) + { + if (m_layers > 0) + glTexStorage3D(target, m_levels, internal_format, m_width, m_height, m_levels); + else + glTexStorage2D(target, m_levels, internal_format, m_width, m_height); + } + + glTexSubImage2D(target, 0, 0, 0, m_width, m_height, format, type, data); + } + else + { + glTexImage2D(target, 0, internal_format, width, height, 0, format, type, data); + } +} + +void Texture::ReplaceImage(u32 layer, u32 level, GLenum format, GLenum type, const void* data) +{ + Assert(IsValid() && !IsMultisampled()); + + const GLenum target = GetGLTarget(); + if (IsTextureArray()) + glTexSubImage3D(target, level, 0, 0, layer, m_width, m_height, 1, format, type, data); + else + glTexSubImage2D(target, level, 0, 0, m_width, m_height, format, type, data); +} + +void Texture::ReplaceSubImage(u32 layer, u32 level, u32 x, u32 y, u32 width, u32 height, GLenum format, GLenum type, + const void* data) +{ + Assert(IsValid() && !IsMultisampled()); + + const GLenum target = GetGLTarget(); + if (IsTextureArray()) + glTexSubImage3D(target, level, x, y, layer, width, height, 1, format, type, data); + else + glTexSubImage2D(target, level, x, y, width, height, format, type, data); } void Texture::SetLinearFilter(bool enabled) @@ -154,6 +250,8 @@ void Texture::Destroy() m_width = 0; m_height = 0; + m_layers = 0; + m_levels = 0; m_samples = 0; } @@ -170,7 +268,7 @@ void Texture::BindFramebuffer(GLenum target /*= GL_DRAW_FRAMEBUFFER*/) void Texture::Unbind() { - glBindTexture(GL_TEXTURE_2D, 0); + glBindTexture(GetGLTarget(), 0); } Texture& Texture::operator=(Texture&& moved) @@ -180,12 +278,16 @@ Texture& Texture::operator=(Texture&& moved) m_id = moved.m_id; m_width = moved.m_width; m_height = moved.m_height; + m_layers = moved.m_layers; + m_levels = moved.m_levels; m_samples = moved.m_samples; m_fbo_id = moved.m_fbo_id; moved.m_id = 0; moved.m_width = 0; moved.m_height = 0; + moved.m_layers = 0; + moved.m_levels = 0; moved.m_samples = 0; moved.m_fbo_id = 0; return *this; diff --git a/src/common/gl/texture.h b/src/common/gl/texture.h index bc22a4362..b9d2d1a1f 100644 --- a/src/common/gl/texture.h +++ b/src/common/gl/texture.h @@ -12,9 +12,12 @@ public: static bool UseTextureStorage(bool multisampled); - bool Create(u32 width, u32 height, u32 samples, GLenum internal_format, GLenum format, GLenum type, - const void* data = nullptr, bool linear_filter = false, bool wrap = false); + bool Create(u32 width, u32 height, u32 layers, u32 levels, u32 samples, GLenum internal_format, GLenum format, + GLenum type, const void* data = nullptr, bool linear_filter = false, bool wrap = false); void Replace(u32 width, u32 height, GLenum internal_format, GLenum format, GLenum type, const void* data); + void ReplaceImage(u32 layer, u32 level, GLenum format, GLenum type, const void* data); + void ReplaceSubImage(u32 layer, u32 level, u32 x, u32 y, u32 width, u32 height, GLenum format, GLenum type, + const void* data); bool CreateFramebuffer(); void Destroy(); @@ -22,20 +25,26 @@ public: bool UseTextureStorage() const; void SetLinearFilter(bool enabled); - bool IsValid() const { return m_id != 0; } - bool IsMultisampled() const { return m_samples > 1; } - GLuint GetGLId() const { return m_id; } - u32 GetWidth() const { return m_width; } - u32 GetHeight() const { return m_height; } - u32 GetSamples() const { return m_samples; } + ALWAYS_INLINE bool IsValid() const { return m_id != 0; } + ALWAYS_INLINE bool IsTextureArray() const { return m_layers > 1; } + ALWAYS_INLINE bool IsMultisampled() const { return m_samples > 1; } + ALWAYS_INLINE GLuint GetGLId() const { return m_id; } + ALWAYS_INLINE u16 GetWidth() const { return m_width; } + ALWAYS_INLINE u16 GetHeight() const { return m_height; } + ALWAYS_INLINE u16 GetLayers() const { return m_layers; } + ALWAYS_INLINE u8 GetLevels() const { return m_levels; } + ALWAYS_INLINE u8 GetSamples() const { return m_samples; } - GLuint GetGLFramebufferID() const { return m_fbo_id; } - GLenum GetGLTarget() const { return IsMultisampled() ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D; } + ALWAYS_INLINE GLuint GetGLFramebufferID() const { return m_fbo_id; } + ALWAYS_INLINE GLenum GetGLTarget() const + { + return (IsMultisampled() ? (IsTextureArray() ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D_MULTISAMPLE_ARRAY) : + (IsTextureArray() ? GL_TEXTURE_2D_ARRAY : GL_TEXTURE_2D)); + } void Bind(); void BindFramebuffer(GLenum target = GL_DRAW_FRAMEBUFFER); - - static void Unbind(); + void Unbind(); Texture& operator=(const Texture& copy) = delete; Texture& operator=(Texture&& moved); @@ -47,9 +56,11 @@ public: private: GLuint m_id = 0; - u32 m_width = 0; - u32 m_height = 0; - u32 m_samples = 0; + u16 m_width = 0; + u16 m_height = 0; + u16 m_layers = 0; + u8 m_levels = 0; + u8 m_samples = 0; GLuint m_fbo_id = 0; }; diff --git a/src/core/gpu_hw_opengl.cpp b/src/core/gpu_hw_opengl.cpp index de36303f7..6010d7335 100644 --- a/src/core/gpu_hw_opengl.cpp +++ b/src/core/gpu_hw_opengl.cpp @@ -49,10 +49,9 @@ bool GPU_HW_OpenGL::Initialize() return false; } - const bool opengl_is_available = - ((g_host_display->GetRenderAPI() == RenderAPI::OpenGL && - (GLAD_GL_VERSION_3_0 || GLAD_GL_ARB_uniform_buffer_object)) || - (g_host_display->GetRenderAPI() == RenderAPI::OpenGLES && GLAD_GL_ES_VERSION_3_0)); + const bool opengl_is_available = ((g_host_display->GetRenderAPI() == RenderAPI::OpenGL && + (GLAD_GL_VERSION_3_0 || GLAD_GL_ARB_uniform_buffer_object)) || + (g_host_display->GetRenderAPI() == RenderAPI::OpenGLES && GLAD_GL_ES_VERSION_3_0)); if (!opengl_is_available) { Host::AddOSDMessage(Host::TranslateStdString("OSDMessage", @@ -397,18 +396,18 @@ bool GPU_HW_OpenGL::CreateFramebuffer() const u32 texture_height = VRAM_HEIGHT * m_resolution_scale; const u32 multisamples = m_multisamples; - if (!m_vram_texture.Create(texture_width, texture_height, multisamples, GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, nullptr, - false, true) || - !m_vram_depth_texture.Create(texture_width, texture_height, multisamples, GL_DEPTH_COMPONENT16, + if (!m_vram_texture.Create(texture_width, texture_height, 1, 1, multisamples, GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, + nullptr, false, true) || + !m_vram_depth_texture.Create(texture_width, texture_height, 1, 1, multisamples, GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, nullptr, false) || - !m_vram_read_texture.Create(texture_width, texture_height, 1, GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, nullptr, false, - true) || + !m_vram_read_texture.Create(texture_width, texture_height, 1, 1, 1, GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, nullptr, + false, true) || !m_vram_read_texture.CreateFramebuffer() || - !m_vram_encoding_texture.Create(VRAM_WIDTH, VRAM_HEIGHT, 1, GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, nullptr, + !m_vram_encoding_texture.Create(VRAM_WIDTH, VRAM_HEIGHT, 1, 1, 1, GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, nullptr, false) || !m_vram_encoding_texture.CreateFramebuffer() || !m_display_texture.Create(GPU_MAX_DISPLAY_WIDTH * m_resolution_scale, GPU_MAX_DISPLAY_HEIGHT * m_resolution_scale, - 1, GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, nullptr, false) || + 1, 1, 1, GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, nullptr, false) || !m_display_texture.CreateFramebuffer()) { return false; @@ -426,7 +425,7 @@ bool GPU_HW_OpenGL::CreateFramebuffer() if (m_downsample_mode == GPUDownsampleMode::Box) { - if (!m_downsample_texture.Create(VRAM_WIDTH, VRAM_HEIGHT, 1, GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE) || + if (!m_downsample_texture.Create(VRAM_WIDTH, VRAM_HEIGHT, 1, 1, 1, GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE) || !m_downsample_texture.CreateFramebuffer()) { return false; @@ -779,7 +778,7 @@ bool GPU_HW_OpenGL::BlitVRAMReplacementTexture(const TextureReplacementTexture* { if (!m_vram_write_replacement_texture.IsValid()) { - if (!m_vram_write_replacement_texture.Create(tex->GetWidth(), tex->GetHeight(), 1, GL_RGBA, GL_RGBA, + if (!m_vram_write_replacement_texture.Create(tex->GetWidth(), tex->GetHeight(), 1, 1, 1, GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, tex->GetPixels(), true) || !m_vram_write_replacement_texture.CreateFramebuffer()) { @@ -789,7 +788,7 @@ bool GPU_HW_OpenGL::BlitVRAMReplacementTexture(const TextureReplacementTexture* } else { - m_vram_write_replacement_texture.Replace(tex->GetWidth(), tex->GetHeight(), GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, + m_vram_write_replacement_texture.Replace(tex->GetWidth(), tex->GetHeight(), GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, tex->GetPixels()); } diff --git a/src/frontend-common/opengl_host_display.cpp b/src/frontend-common/opengl_host_display.cpp index c3ca59991..0f10525bb 100644 --- a/src/frontend-common/opengl_host_display.cpp +++ b/src/frontend-common/opengl_host_display.cpp @@ -106,7 +106,7 @@ std::unique_ptr OpenGLHostDisplay::CreateTexture(u32 width, Assert(!data || data_stride == (width * sizeof(u32))); GL::Texture tex; - if (!tex.Create(width, height, samples, gl_internal_format, gl_format, gl_type, data, data_stride)) + if (!tex.Create(width, height, layers, levels, samples, gl_internal_format, gl_format, gl_type, data, data_stride)) return {}; return std::make_unique(std::move(tex), format); @@ -600,8 +600,11 @@ bool OpenGLHostDisplay::RenderScreenshot(u32 width, u32 height, std::vector HostDisplayPixelFormat* out_format) { GL::Texture texture; - if (!texture.Create(width, height, 1, GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, nullptr) || !texture.CreateFramebuffer()) + if (!texture.Create(width, height, 1, 1, 1, GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, nullptr) || + !texture.CreateFramebuffer()) + { return false; + } glDisable(GL_SCISSOR_TEST); texture.BindFramebuffer(GL_FRAMEBUFFER); @@ -842,7 +845,8 @@ bool OpenGLHostDisplay::CheckPostProcessingRenderTargets(u32 target_width, u32 t if (m_post_processing_input_texture.GetWidth() != target_width || m_post_processing_input_texture.GetHeight() != target_height) { - if (!m_post_processing_input_texture.Create(target_width, target_height, 1, GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE) || + if (!m_post_processing_input_texture.Create(target_width, target_height, 1, 1, 1, GL_RGBA8, GL_RGBA, + GL_UNSIGNED_BYTE) || !m_post_processing_input_texture.CreateFramebuffer()) { return false; @@ -855,7 +859,7 @@ bool OpenGLHostDisplay::CheckPostProcessingRenderTargets(u32 target_width, u32 t PostProcessingStage& pps = m_post_processing_stages[i]; if (pps.output_texture.GetWidth() != target_width || pps.output_texture.GetHeight() != target_height) { - if (!pps.output_texture.Create(target_width, target_height, 1, GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE) || + if (!pps.output_texture.Create(target_width, target_height, 1, 1, 1, GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE) || !pps.output_texture.CreateFramebuffer()) { return false; @@ -1150,7 +1154,7 @@ void OpenGLHostDisplayTexture::EndUpdate(u32 x, u32 y, u32 width, u32 height) const bool whole_texture = (!m_texture.UseTextureStorage() && x == 0 && y == 0 && width == m_texture.GetWidth() && height == m_texture.GetHeight()); - m_texture.Create(width, height, 1, gl_internal_format, gl_format, gl_type, nullptr, false, false); + m_texture.Create(width, height, 1, 1, 1, gl_internal_format, gl_format, gl_type, nullptr, false, false); m_texture.Bind(); if (buffer && size_required < buffer->GetSize()) {