renderer_gl: Initial MSAA implementation

Basically copied over some of the paradigms over from Vulkan. Covers most rendering uses-cases except for conversions such as `ConvertDS24S8ToRGBA8` and `ConvertRGBA4ToRGB5A1`
This commit is contained in:
Wunkolo 2026-05-09 20:24:34 -07:00
parent 29954c1392
commit 2743ebd0c9
7 changed files with 130 additions and 45 deletions

View file

@ -19,10 +19,9 @@ ConfigureEnhancements::ConfigureEnhancements(QWidget* parent)
SetConfiguration();
const auto graphics_api = Settings::values.graphics_api.GetValue();
const bool res_scale_enabled = graphics_api != Settings::GraphicsAPI::Software;
ui->resolution_factor_combobox->setEnabled(res_scale_enabled);
const bool msaa_enabled = graphics_api == Settings::GraphicsAPI::Vulkan;
ui->antialiasing_combobox->setEnabled(msaa_enabled);
const bool hardware_graphics = graphics_api != Settings::GraphicsAPI::Software;
ui->resolution_factor_combobox->setEnabled(hardware_graphics);
ui->antialiasing_combobox->setEnabled(hardware_graphics);
connect(ui->render_3d_combobox, qOverload<int>(&QComboBox::currentIndexChanged), this,
[this](int currentIndex) {

View file

@ -30,13 +30,6 @@ u32 RendererBase::GetResolutionScaleFactor() {
}
u8 RendererBase::GetSampleCount() const {
const auto graphics_api = Settings::values.graphics_api.GetValue();
// Enabled for vulkan only for now
if (graphics_api != Settings::GraphicsAPI::Vulkan) {
return 1;
}
return Settings::GetAntiAliasingSampleCount(Settings::values.antialiasing.GetValue());
}

View file

@ -67,6 +67,7 @@ BlitHelper::BlitHelper(const Driver& driver_)
d24s8_to_rgba8{CreateProgram(HostShaders::D24S8_TO_RGBA8_FRAG, "D24S8_TO_RGBA8_FRAG")},
rgba4_to_rgb5a1{CreateProgram(HostShaders::RGBA4_TO_RGB5A1_FRAG, "RGBA4_TO_RGB5A1_FRAG")} {
vao.Create();
read_fbo.Create();
draw_fbo.Create();
state.draw.vertex_array = vao.handle;
for (u32 i = 0; i < 3; i++) {
@ -156,6 +157,22 @@ bool BlitHelper::ConvertRGBA4ToRGB5A1(Surface& source, Surface& dest,
return true;
}
void BlitHelper::ResolveTexture(Surface& surface) {
state.draw.read_framebuffer = read_fbo.handle;
state.draw.draw_framebuffer = draw_fbo.handle;
state.Apply();
surface.Attach(GL_READ_FRAMEBUFFER, 0, 0, 3);
surface.Attach(GL_DRAW_FRAMEBUFFER, 0, 0, 1);
const GLbitfield buffer_mask = surface.type == SurfaceType::Depth ? GL_DEPTH_BUFFER_BIT
: surface.type == SurfaceType::DepthStencil
? (GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
: GL_COLOR_BUFFER_BIT;
glBlitFramebuffer(0, 0, surface.GetScaledWidth(), surface.GetScaledHeight(), 0, 0,
surface.GetScaledWidth(), surface.GetScaledHeight(), buffer_mask, GL_NEAREST);
}
bool BlitHelper::Filter(Surface& surface, const VideoCore::TextureBlit& blit) {
const auto filter = Settings::values.texture_filter.GetValue();
const bool is_depth =

View file

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -31,6 +31,8 @@ public:
bool ConvertRGBA4ToRGB5A1(Surface& source, Surface& dest, const VideoCore::TextureCopy& copy);
void ResolveTexture(Surface& surface);
private:
void FilterAnime4K(Surface& surface, const VideoCore::TextureBlit& blit);
void FilterBicubic(Surface& surface, const VideoCore::TextureBlit& blit);
@ -47,6 +49,7 @@ private:
const Driver& driver;
OGLVertexArray vao;
OpenGLState state;
OGLFramebuffer read_fbo;
OGLFramebuffer draw_fbo;
OGLSampler linear_sampler;
OGLSampler nearest_sampler;

View file

@ -164,6 +164,10 @@ RasterizerOpenGL::RasterizerOpenGL(Memory::MemorySystem& memory, Pica::PicaCore&
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buffer.GetHandle());
glEnable(GL_BLEND);
glEnable(GL_MULTISAMPLE);
glEnable(GL_SAMPLE_SHADING);
glMinSampleShading(1.0f);
}
RasterizerOpenGL::~RasterizerOpenGL() = default;

View file

@ -92,13 +92,20 @@ static constexpr std::array<FormatTuple, 8> CUSTOM_TUPLES = {{
return 0;
}
[[nodiscard]] OGLTexture MakeHandle(GLenum target, u32 width, u32 height, u32 levels,
[[nodiscard]] OGLTexture MakeHandle(GLenum target, u32 width, u32 height, u32 levels, u32 samples,
const FormatTuple& tuple, std::string_view debug_name = "") {
OGLTexture texture{};
texture.Create();
glBindTexture(target, texture.handle);
glTexStorage2D(target, levels, tuple.internal_format, width, height);
if (samples > 1) {
ASSERT(target == GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, texture.handle);
glTexStorage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, samples, tuple.internal_format, width,
height, false);
} else {
glBindTexture(target, texture.handle);
glTexStorage2D(target, levels, tuple.internal_format, width, height);
}
glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
@ -215,6 +222,14 @@ bool TextureRuntime::ClearTextureWithoutFbo(Surface& surface,
glClearTexSubImage(surface.Handle(), clear.texture_level, clear.texture_rect.left,
clear.texture_rect.bottom, 0, clear.texture_rect.GetWidth(),
clear.texture_rect.GetHeight(), 1, format, type, &clear.value);
if (surface.sample_count > 1) {
// Clear MSAA too
glClearTexSubImage(surface.Handle(3), clear.texture_level, clear.texture_rect.left,
clear.texture_rect.bottom, 0, clear.texture_rect.GetWidth(),
clear.texture_rect.GetHeight(), 1, format, type, &clear.value);
}
return true;
}
@ -279,6 +294,15 @@ bool TextureRuntime::CopyTextures(Surface& source, Surface& dest,
bool TextureRuntime::BlitTextures(Surface& source, Surface& dest,
const VideoCore::TextureBlit& blit) {
// Must resolve images first
// Todo(wunk): Add a "dirty" flag for msaa resolves to avoid redundant image resolves
if (source.sample_count > 1) {
blit_helper.ResolveTexture(source);
}
if (dest.sample_count > 1) {
blit_helper.ResolveTexture(dest);
}
OpenGLState state = OpenGLState::GetCurState();
state.scissor.enabled = false;
state.draw.read_framebuffer = read_fbos[FboIndex(source.type)].handle;
@ -329,11 +353,16 @@ Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceParams& param
const GLenum target =
texture_type == VideoCore::TextureType::CubeMap ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D;
textures[0] = MakeHandle(target, width, height, levels, tuple, DebugName(false));
textures[0] = MakeHandle(target, width, height, levels, 1, tuple, DebugName(false));
if (res_scale != 1) {
textures[1] = MakeHandle(target, GetScaledWidth(), GetScaledHeight(), levels, tuple,
textures[1] = MakeHandle(target, GetScaledWidth(), GetScaledHeight(), levels, 1, tuple,
DebugName(true, false));
}
if (sample_count > 1) {
textures[3] = MakeHandle(target, GetScaledWidth(), GetScaledHeight(), levels, sample_count,
tuple, DebugName(true, false, sample_count));
}
}
Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceBase& surface,
@ -351,15 +380,19 @@ Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceBase& surface
custom_format = mat->format;
material = mat;
textures[0] = MakeHandle(target, mat->width, mat->height, levels, tuple, DebugName(false));
textures[0] = MakeHandle(target, mat->width, mat->height, levels, 1, tuple, DebugName(false));
if (res_scale != 1) {
textures[1] = MakeHandle(target, mat->width, mat->height, levels, DEFAULT_TUPLE,
textures[1] = MakeHandle(target, mat->width, mat->height, levels, 1, DEFAULT_TUPLE,
DebugName(true, true));
}
const bool has_normal = mat->Map(MapType::Normal);
if (has_normal) {
textures[2] =
MakeHandle(target, mat->width, mat->height, levels, tuple, DebugName(true, true));
MakeHandle(target, mat->width, mat->height, levels, 1, tuple, DebugName(true, true));
}
if (sample_count > 1) {
textures[3] = MakeHandle(target, mat->width, mat->height, sample_count, levels,
DEFAULT_TUPLE, DebugName(true, true, sample_count));
}
}
@ -374,8 +407,8 @@ GLuint Surface::Handle(u32 index) const noexcept {
GLuint Surface::CopyHandle() noexcept {
if (!copy_texture.handle) {
copy_texture = MakeHandle(GL_TEXTURE_2D, GetScaledWidth(), GetScaledHeight(), levels, tuple,
DebugName(true));
copy_texture = MakeHandle(GL_TEXTURE_2D, GetScaledWidth(), GetScaledHeight(), levels, 1,
tuple, DebugName(true));
}
for (u32 level = 0; level < levels; level++) {
@ -534,22 +567,26 @@ bool Surface::DownloadWithoutFbo(const VideoCore::BufferTextureCopy& download,
return false;
}
void Surface::Attach(GLenum target, u32 level, u32 layer, bool scaled) {
const GLuint handle = Handle(static_cast<u32>(scaled));
const GLenum textarget = texture_type == TextureType::CubeMap
? GL_TEXTURE_CUBE_MAP_POSITIVE_X + layer
: GL_TEXTURE_2D;
void Surface::Attach(GLenum target, u32 level, u32 layer, u32 handle) {
const GLuint gl_handle = Handle(handle);
GLenum textarget = texture_type == TextureType::CubeMap ? GL_TEXTURE_CUBE_MAP_POSITIVE_X + layer
: GL_TEXTURE_2D;
if (handle == 3 && sample_count > 1) {
ASSERT(texture_type == TextureType::Texture2D);
textarget = GL_TEXTURE_2D_MULTISAMPLE;
}
switch (type) {
case SurfaceType::Color:
case SurfaceType::Texture:
glFramebufferTexture2D(target, GL_COLOR_ATTACHMENT0, textarget, handle, level);
glFramebufferTexture2D(target, GL_COLOR_ATTACHMENT0, textarget, gl_handle, level);
break;
case SurfaceType::Depth:
glFramebufferTexture2D(target, GL_DEPTH_ATTACHMENT, textarget, handle, level);
glFramebufferTexture2D(target, GL_DEPTH_ATTACHMENT, textarget, gl_handle, level);
break;
case SurfaceType::DepthStencil:
glFramebufferTexture2D(target, GL_DEPTH_STENCIL_ATTACHMENT, textarget, handle, level);
glFramebufferTexture2D(target, GL_DEPTH_STENCIL_ATTACHMENT, textarget, gl_handle, level);
break;
default:
UNREACHABLE_MSG("Invalid surface type!");
@ -557,17 +594,11 @@ void Surface::Attach(GLenum target, u32 level, u32 layer, bool scaled) {
}
void Surface::ScaleUp(u32 new_scale, u8 new_sample_count) {
if (res_scale == new_scale && sample_count == new_sample_count) {
return;
}
const bool res_scale_modified = res_scale != new_scale;
if (res_scale_modified && new_scale > 1) {
res_scale = new_scale;
sample_count = new_sample_count;
if (res_scale > 1) {
textures[1] = MakeHandle(GL_TEXTURE_2D, GetScaledWidth(), GetScaledHeight(), levels, tuple,
DebugName(true));
textures[1] = MakeHandle(GL_TEXTURE_2D, GetScaledWidth(), GetScaledHeight(), levels, 1,
tuple, DebugName(true));
for (u32 level = 0; level < levels; level++) {
const VideoCore::TextureBlit blit = {
.src_level = level,
@ -579,8 +610,10 @@ void Surface::ScaleUp(u32 new_scale, u8 new_sample_count) {
}
}
if (new_sample_count > 1) {
if ((res_scale_modified || sample_count != new_sample_count) && new_sample_count > 1) {
// Todo(wunk): OpenGL MSAA
textures[3] = MakeHandle(GL_TEXTURE_2D, GetScaledWidth(), GetScaledHeight(), levels,
sample_count, tuple, DebugName(true));
}
}
@ -614,7 +647,8 @@ void Surface::BlitScale(const VideoCore::TextureBlit& blit, bool up_scale) {
Framebuffer::Framebuffer(TextureRuntime& runtime, const VideoCore::FramebufferParams& params,
const Surface* color, const Surface* depth)
: VideoCore::FramebufferParams{params},
res_scale{color ? color->res_scale : (depth ? depth->res_scale : 1u)} {
res_scale{color ? color->res_scale : (depth ? depth->res_scale : 1u)},
sample_count{color ? color->sample_count : (depth ? depth->sample_count : 1u)} {
if (shadow_rendering && !color) {
return;
@ -627,6 +661,15 @@ Framebuffer::Framebuffer(TextureRuntime& runtime, const VideoCore::FramebufferPa
attachments[1] = depth->Handle();
}
if (sample_count > 1) {
if (color) {
attachments[2] = color->Handle(3);
}
if (depth) {
attachments[3] = depth->Handle(3);
}
}
framebuffer.Create();
OpenGLState state = OpenGLState::GetCurState();
@ -658,6 +701,27 @@ Framebuffer::Framebuffer(TextureRuntime& runtime, const VideoCore::FramebufferPa
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D,
0, 0);
}
if (sample_count > 1) {
if (color) {
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D_MULTISAMPLE, color ? color->Handle(3) : 0,
color_level);
}
if (depth) {
if (depth->pixel_format == PixelFormat::D24S8) {
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
GL_TEXTURE_2D_MULTISAMPLE, depth->Handle(3),
depth_level);
} else {
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
GL_TEXTURE_2D_MULTISAMPLE, depth->Handle(3),
depth_level);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
GL_TEXTURE_2D_MULTISAMPLE, 0, 0);
}
}
}
}
}

View file

@ -130,7 +130,7 @@ public:
const VideoCore::StagingData& staging);
/// Attaches a handle of surface to the specified framebuffer target
void Attach(GLenum target, u32 level, u32 layer, bool scaled = true);
void Attach(GLenum target, u32 level, u32 layer, u32 handle = 1);
/// Scales up the surface to match the new resolution scale and sample-count.
void ScaleUp(u32 new_scale, u8 new_sample_count);
@ -149,7 +149,7 @@ private:
private:
const Driver* driver;
TextureRuntime* runtime;
std::array<OGLTexture, 3> textures;
std::array<OGLTexture, 4> textures;
OGLTexture copy_texture;
FormatTuple tuple;
};
@ -170,6 +170,10 @@ public:
return res_scale;
}
[[nodiscard]] u32 Samples() const noexcept {
return sample_count;
}
[[nodiscard]] GLuint Handle() const noexcept {
return framebuffer.handle;
}
@ -184,7 +188,8 @@ public:
private:
u32 res_scale{1};
std::array<GLuint, 2> attachments{};
u32 sample_count{1};
std::array<GLuint, 4> attachments{};
OGLFramebuffer framebuffer;
};