GL/Texture: Add multi-layer/level support

This commit is contained in:
Connor McLaughlin 2021-06-06 16:35:47 +10:00
parent 5ef0ad1ec6
commit e7fb42347f
4 changed files with 168 additions and 52 deletions

View file

@ -1,10 +1,15 @@
#include "texture.h" #include "texture.h"
#include "../assert.h" #include "../assert.h"
#include "../log.h" #include "../log.h"
#include <limits>
Log_SetChannel(GL); Log_SetChannel(GL);
namespace GL { namespace GL {
static constexpr u32 MAX_DIMENSIONS = std::numeric_limits<u16>::max();
static constexpr u8 MAX_LEVELS = std::numeric_limits<u8>::max();
static constexpr u8 MAX_SAMPLES = std::numeric_limits<u8>::max();
Texture::Texture() = default; Texture::Texture() = default;
Texture::Texture(Texture&& moved) Texture::Texture(Texture&& moved)
@ -33,12 +38,33 @@ bool Texture::UseTextureStorage() const
return UseTextureStorage(IsMultisampled()); return UseTextureStorage(IsMultisampled());
} }
bool Texture::Create(u32 width, u32 height, u32 samples, GLenum internal_format, GLenum format, GLenum type, bool Texture::Create(u32 width, u32 height, u32 layers, u32 levels, u32 samples, GLenum internal_format, GLenum format,
const void* data, bool linear_filter, bool wrap) GLenum type, const void* data /* = nullptr */, bool linear_filter /* = false */,
bool wrap /* = false */)
{ {
glGetError(); 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; GLuint id;
glGenTextures(1, &id); glGenTextures(1, &id);
@ -48,27 +74,51 @@ bool Texture::Create(u32 width, u32 height, u32 samples, GLenum internal_format,
{ {
Assert(!data); Assert(!data);
if (UseTextureStorage(true)) if (UseTextureStorage(true))
{
if (layers > 1)
glTexStorage3DMultisample(target, samples, internal_format, width, height, layers, GL_FALSE);
else
glTexStorage2DMultisample(target, samples, internal_format, width, height, GL_FALSE); glTexStorage2DMultisample(target, samples, internal_format, width, height, GL_FALSE);
}
else
{
if (layers > 1)
glTexImage3DMultisample(target, samples, internal_format, width, height, layers, GL_FALSE);
else else
glTexImage2DMultisample(target, samples, internal_format, width, height, GL_FALSE); glTexImage2DMultisample(target, samples, internal_format, width, height, GL_FALSE);
} }
}
else else
{ {
if (UseTextureStorage(false)) if (UseTextureStorage(false))
{ {
glTexStorage2D(target, 1, internal_format, width, height); if (layers > 1)
if (data) glTexStorage3D(target, levels, internal_format, width, height, layers);
glTexSubImage2D(target, 0, 0, 0, width, height, format, type, data); else
glTexStorage2D(target, levels, internal_format, width, height);
} }
else 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_MIN_FILTER, linear_filter ? GL_LINEAR : GL_NEAREST);
glTexParameteri(target, GL_TEXTURE_MAG_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_S, wrap ? GL_REPEAT : GL_CLAMP_TO_EDGE);
glTexParameteri(target, GL_TEXTURE_WRAP_T, 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. // This doesn't exist on GLES2.
@ -87,21 +137,67 @@ bool Texture::Create(u32 width, u32 height, u32 samples, GLenum internal_format,
Destroy(); Destroy();
m_id = id; m_id = id;
m_width = width; m_width = static_cast<u16>(width);
m_height = height; m_height = static_cast<u16>(height);
m_samples = samples; m_layers = static_cast<u16>(layers);
m_levels = static_cast<u8>(levels);
m_samples = static_cast<u8>(samples);
return true; return true;
} }
void Texture::Replace(u32 width, u32 height, GLenum internal_format, GLenum format, GLenum type, const void* data) 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; const bool size_changed = (width != m_width || height != m_height);
m_height = height;
glBindTexture(GL_TEXTURE_2D, m_id); m_width = static_cast<u16>(width);
glTexImage2D(GL_TEXTURE_2D, 0, internal_format, width, height, 0, format, type, data); m_height = static_cast<u16>(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) void Texture::SetLinearFilter(bool enabled)
@ -154,6 +250,8 @@ void Texture::Destroy()
m_width = 0; m_width = 0;
m_height = 0; m_height = 0;
m_layers = 0;
m_levels = 0;
m_samples = 0; m_samples = 0;
} }
@ -170,7 +268,7 @@ void Texture::BindFramebuffer(GLenum target /*= GL_DRAW_FRAMEBUFFER*/)
void Texture::Unbind() void Texture::Unbind()
{ {
glBindTexture(GL_TEXTURE_2D, 0); glBindTexture(GetGLTarget(), 0);
} }
Texture& Texture::operator=(Texture&& moved) Texture& Texture::operator=(Texture&& moved)
@ -180,12 +278,16 @@ Texture& Texture::operator=(Texture&& moved)
m_id = moved.m_id; m_id = moved.m_id;
m_width = moved.m_width; m_width = moved.m_width;
m_height = moved.m_height; m_height = moved.m_height;
m_layers = moved.m_layers;
m_levels = moved.m_levels;
m_samples = moved.m_samples; m_samples = moved.m_samples;
m_fbo_id = moved.m_fbo_id; m_fbo_id = moved.m_fbo_id;
moved.m_id = 0; moved.m_id = 0;
moved.m_width = 0; moved.m_width = 0;
moved.m_height = 0; moved.m_height = 0;
moved.m_layers = 0;
moved.m_levels = 0;
moved.m_samples = 0; moved.m_samples = 0;
moved.m_fbo_id = 0; moved.m_fbo_id = 0;
return *this; return *this;

View file

@ -12,9 +12,12 @@ public:
static bool UseTextureStorage(bool multisampled); static bool UseTextureStorage(bool multisampled);
bool Create(u32 width, u32 height, u32 samples, GLenum internal_format, GLenum format, GLenum type, bool Create(u32 width, u32 height, u32 layers, u32 levels, u32 samples, GLenum internal_format, GLenum format,
const void* data = nullptr, bool linear_filter = false, bool wrap = false); 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 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(); bool CreateFramebuffer();
void Destroy(); void Destroy();
@ -22,20 +25,26 @@ public:
bool UseTextureStorage() const; bool UseTextureStorage() const;
void SetLinearFilter(bool enabled); void SetLinearFilter(bool enabled);
bool IsValid() const { return m_id != 0; } ALWAYS_INLINE bool IsValid() const { return m_id != 0; }
bool IsMultisampled() const { return m_samples > 1; } ALWAYS_INLINE bool IsTextureArray() const { return m_layers > 1; }
GLuint GetGLId() const { return m_id; } ALWAYS_INLINE bool IsMultisampled() const { return m_samples > 1; }
u32 GetWidth() const { return m_width; } ALWAYS_INLINE GLuint GetGLId() const { return m_id; }
u32 GetHeight() const { return m_height; } ALWAYS_INLINE u16 GetWidth() const { return m_width; }
u32 GetSamples() const { return m_samples; } 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; } ALWAYS_INLINE GLuint GetGLFramebufferID() const { return m_fbo_id; }
GLenum GetGLTarget() const { return IsMultisampled() ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D; } 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 Bind();
void BindFramebuffer(GLenum target = GL_DRAW_FRAMEBUFFER); void BindFramebuffer(GLenum target = GL_DRAW_FRAMEBUFFER);
void Unbind();
static void Unbind();
Texture& operator=(const Texture& copy) = delete; Texture& operator=(const Texture& copy) = delete;
Texture& operator=(Texture&& moved); Texture& operator=(Texture&& moved);
@ -47,9 +56,11 @@ public:
private: private:
GLuint m_id = 0; GLuint m_id = 0;
u32 m_width = 0; u16 m_width = 0;
u32 m_height = 0; u16 m_height = 0;
u32 m_samples = 0; u16 m_layers = 0;
u8 m_levels = 0;
u8 m_samples = 0;
GLuint m_fbo_id = 0; GLuint m_fbo_id = 0;
}; };

View file

@ -49,8 +49,7 @@ bool GPU_HW_OpenGL::Initialize()
return false; return false;
} }
const bool opengl_is_available = const bool opengl_is_available = ((g_host_display->GetRenderAPI() == RenderAPI::OpenGL &&
((g_host_display->GetRenderAPI() == RenderAPI::OpenGL &&
(GLAD_GL_VERSION_3_0 || GLAD_GL_ARB_uniform_buffer_object)) || (GLAD_GL_VERSION_3_0 || GLAD_GL_ARB_uniform_buffer_object)) ||
(g_host_display->GetRenderAPI() == RenderAPI::OpenGLES && GLAD_GL_ES_VERSION_3_0)); (g_host_display->GetRenderAPI() == RenderAPI::OpenGLES && GLAD_GL_ES_VERSION_3_0));
if (!opengl_is_available) if (!opengl_is_available)
@ -397,18 +396,18 @@ bool GPU_HW_OpenGL::CreateFramebuffer()
const u32 texture_height = VRAM_HEIGHT * m_resolution_scale; const u32 texture_height = VRAM_HEIGHT * m_resolution_scale;
const u32 multisamples = m_multisamples; const u32 multisamples = m_multisamples;
if (!m_vram_texture.Create(texture_width, texture_height, multisamples, GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, nullptr, if (!m_vram_texture.Create(texture_width, texture_height, 1, 1, multisamples, GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE,
false, true) || nullptr, false, true) ||
!m_vram_depth_texture.Create(texture_width, texture_height, multisamples, GL_DEPTH_COMPONENT16, !m_vram_depth_texture.Create(texture_width, texture_height, 1, 1, multisamples, GL_DEPTH_COMPONENT16,
GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, nullptr, false) || 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, !m_vram_read_texture.Create(texture_width, texture_height, 1, 1, 1, GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, nullptr,
true) || false, true) ||
!m_vram_read_texture.CreateFramebuffer() || !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) || false) ||
!m_vram_encoding_texture.CreateFramebuffer() || !m_vram_encoding_texture.CreateFramebuffer() ||
!m_display_texture.Create(GPU_MAX_DISPLAY_WIDTH * m_resolution_scale, GPU_MAX_DISPLAY_HEIGHT * m_resolution_scale, !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()) !m_display_texture.CreateFramebuffer())
{ {
return false; return false;
@ -426,7 +425,7 @@ bool GPU_HW_OpenGL::CreateFramebuffer()
if (m_downsample_mode == GPUDownsampleMode::Box) 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()) !m_downsample_texture.CreateFramebuffer())
{ {
return false; 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.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) || GL_UNSIGNED_BYTE, tex->GetPixels(), true) ||
!m_vram_write_replacement_texture.CreateFramebuffer()) !m_vram_write_replacement_texture.CreateFramebuffer())
{ {
@ -789,7 +788,7 @@ bool GPU_HW_OpenGL::BlitVRAMReplacementTexture(const TextureReplacementTexture*
} }
else 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()); tex->GetPixels());
} }

View file

@ -106,7 +106,7 @@ std::unique_ptr<HostDisplayTexture> OpenGLHostDisplay::CreateTexture(u32 width,
Assert(!data || data_stride == (width * sizeof(u32))); Assert(!data || data_stride == (width * sizeof(u32)));
GL::Texture tex; 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 {};
return std::make_unique<OpenGLHostDisplayTexture>(std::move(tex), format); return std::make_unique<OpenGLHostDisplayTexture>(std::move(tex), format);
@ -600,8 +600,11 @@ bool OpenGLHostDisplay::RenderScreenshot(u32 width, u32 height, std::vector<u32>
HostDisplayPixelFormat* out_format) HostDisplayPixelFormat* out_format)
{ {
GL::Texture texture; 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; return false;
}
glDisable(GL_SCISSOR_TEST); glDisable(GL_SCISSOR_TEST);
texture.BindFramebuffer(GL_FRAMEBUFFER); 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 || if (m_post_processing_input_texture.GetWidth() != target_width ||
m_post_processing_input_texture.GetHeight() != target_height) 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()) !m_post_processing_input_texture.CreateFramebuffer())
{ {
return false; return false;
@ -855,7 +859,7 @@ bool OpenGLHostDisplay::CheckPostProcessingRenderTargets(u32 target_width, u32 t
PostProcessingStage& pps = m_post_processing_stages[i]; PostProcessingStage& pps = m_post_processing_stages[i];
if (pps.output_texture.GetWidth() != target_width || pps.output_texture.GetHeight() != target_height) 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()) !pps.output_texture.CreateFramebuffer())
{ {
return false; 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() && const bool whole_texture = (!m_texture.UseTextureStorage() && x == 0 && y == 0 && width == m_texture.GetWidth() &&
height == m_texture.GetHeight()); 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(); m_texture.Bind();
if (buffer && size_required < buffer->GetSize()) if (buffer && size_required < buffer->GetSize())
{ {