rasterizer_cache: Initial support for multi-sample surfaces

This commit is contained in:
Wunkolo 2026-04-18 22:33:24 -07:00
parent 29eb887d90
commit ddc81c390c
9 changed files with 109 additions and 55 deletions

View file

@ -27,7 +27,8 @@ struct FramebufferParams {
u32 color_level;
u32 depth_level;
bool shadow_rendering;
INSERT_PADDING_BYTES(3);
u8 sample_count;
INSERT_PADDING_BYTES(2);
bool operator==(const FramebufferParams& params) const noexcept {
return std::memcmp(this, &params, sizeof(FramebufferParams)) == 0;

View file

@ -38,7 +38,7 @@ RasterizerCache<T>::RasterizerCache(Memory::MemorySystem& memory_,
Pica::RegsInternal& regs_, RendererBase& renderer_)
: memory{memory_}, custom_tex_manager{custom_tex_manager_}, runtime{runtime_}, regs{regs_},
renderer{renderer_}, resolution_scale_factor{renderer.GetResolutionScaleFactor()},
filter{Settings::values.texture_filter.GetValue()},
sample_count{renderer.GetSampleCount()}, filter{Settings::values.texture_filter.GetValue()},
dump_textures{Settings::values.dump_textures.GetValue()},
use_custom_textures{Settings::values.custom_textures.GetValue()} {
using TextureConfig = Pica::TexturingRegs::TextureConfig;
@ -96,12 +96,15 @@ void RasterizerCache<T>::TickFrame() {
}
const u32 scale_factor = renderer.GetResolutionScaleFactor();
const u32 samples = renderer.GetSampleCount();
const bool resolution_scale_changed = resolution_scale_factor != scale_factor;
const bool sample_count_changed = sample_count != samples;
const bool use_custom_texture_changed =
Settings::values.custom_textures.GetValue() != use_custom_textures;
if (resolution_scale_changed || use_custom_texture_changed) {
if (resolution_scale_changed || use_custom_texture_changed || sample_count_changed) {
resolution_scale_factor = scale_factor;
sample_count = renderer.GetSampleCount();
use_custom_textures = Settings::values.custom_textures.GetValue();
if (use_custom_textures) {
custom_tex_manager.FindCustomTextures();
@ -287,6 +290,7 @@ bool RasterizerCache<T>::AccelerateDisplayTransfer(const Pica::DisplayTransferCo
: config.output_height.Value();
dst_params.is_tiled = config.input_linear != config.dont_swizzle;
dst_params.pixel_format = PixelFormatFromGPUPixelFormat(config.output_format);
dst_params.sample_count = sample_count;
dst_params.UpdateParams();
// Using flip_vertically alongside crop_input_lines produces skewed output on hardware.
@ -302,6 +306,7 @@ bool RasterizerCache<T>::AccelerateDisplayTransfer(const Pica::DisplayTransferCo
}
dst_params.res_scale = slot_surfaces[src_surface_id].res_scale;
dst_params.sample_count = slot_surfaces[src_surface_id].sample_count;
const auto [dst_surface_id, dst_rect] =
GetSurfaceSubRect(dst_params, ScaleMatch::Upscale, false);
@ -432,8 +437,10 @@ void RasterizerCache<T>::CopySurface(Surface& src_surface, Surface& dst_surface,
const u32 src_scale = src_surface.res_scale;
const u32 dst_scale = dst_surface.res_scale;
if (src_scale > dst_scale) {
dst_surface.ScaleUp(src_scale);
const u32 src_sample_count = src_surface.sample_count;
const u32 dst_sample_count = dst_surface.sample_count;
if ((src_scale > dst_scale) || (src_sample_count > dst_sample_count)) {
dst_surface.ScaleUp(src_scale, src_sample_count);
}
const auto src_rect = src_surface.GetScaledSubRect(subrect_params);
@ -502,6 +509,7 @@ typename RasterizerCache<T>::SurfaceRect_Tuple RasterizerCache<T>::GetSurfaceSub
if (surface_id) {
SurfaceParams new_params = slot_surfaces[surface_id];
new_params.res_scale = params.res_scale;
new_params.sample_count = params.sample_count;
surface_id = CreateSurface(new_params, create_initial_flags);
RegisterSurface(surface_id);
@ -706,6 +714,7 @@ FramebufferHelper<T> RasterizerCache<T>::GetFramebufferSurfaces(bool using_color
SurfaceParams color_params;
color_params.is_tiled = true;
color_params.res_scale = resolution_scale_factor;
color_params.sample_count = sample_count;
color_params.width = config.GetWidth();
color_params.height = config.GetHeight();
SurfaceParams depth_params = color_params;
@ -771,6 +780,7 @@ FramebufferHelper<T> RasterizerCache<T>::GetFramebufferSurfaces(bool using_color
.color_level = color_level,
.depth_level = depth_level,
.shadow_rendering = regs.framebuffer.IsShadowRendering(),
.sample_count = sample_count,
};
auto [it, new_framebuffer] = framebuffers.try_emplace(fb_params);
@ -861,12 +871,16 @@ SurfaceId RasterizerCache<T>::FindMatch(const SurfaceParams& params, ScaleMatch
SurfaceId match_id{};
bool match_valid = false;
u32 match_scale = 0;
u8 match_sample_count = 0;
SurfaceInterval match_interval{};
ForEachSurfaceInRegion(params.addr, params.size, [&](SurfaceId surface_id, Surface& surface) {
const bool res_scale_matched = match_scale_type == ScaleMatch::Exact
? (params.res_scale == surface.res_scale)
: (params.res_scale <= surface.res_scale);
const bool sample_count_matched = match_scale_type == ScaleMatch::Exact
? (params.sample_count == surface.sample_count)
: (params.sample_count <= surface.sample_count);
const bool is_valid =
True(find_flags & MatchFlags::Copy)
? true
@ -886,11 +900,16 @@ SurfaceId RasterizerCache<T>::FindMatch(const SurfaceParams& params, ScaleMatch
surface.type != SurfaceType::Fill)
return;
if (!sample_count_matched && match_scale_type != ScaleMatch::Ignore &&
surface.type != SurfaceType::Fill)
return;
// Found a match, update only if this is better than the previous one
auto UpdateMatch = [&] {
match_id = surface_id;
match_valid = is_valid;
match_scale = surface.res_scale;
match_sample_count = surface.sample_count;
match_interval = surface_interval;
};
@ -901,6 +920,13 @@ SurfaceId RasterizerCache<T>::FindMatch(const SurfaceParams& params, ScaleMatch
return;
}
if (surface.sample_count > match_sample_count) {
UpdateMatch();
return;
} else if (surface.sample_count < match_sample_count) {
return;
}
if (is_valid && !match_valid) {
UpdateMatch();
return;
@ -1189,8 +1215,9 @@ bool RasterizerCache<T>::ValidateByReinterpretation(Surface& surface, SurfacePar
return false;
}
const u32 res_scale = src_surface.res_scale;
if (res_scale > surface.res_scale) {
surface.ScaleUp(res_scale);
const u8 sample_count = src_surface.sample_count;
if ((res_scale > surface.res_scale) || (sample_count > surface.sample_count)) {
surface.ScaleUp(res_scale, sample_count);
}
const PAddr addr = boost::icl::lower(interval);
const SurfaceParams copy_params = surface.FromInterval(copy_interval);
@ -1357,8 +1384,8 @@ SurfaceId RasterizerCache<T>::CreateSurface(const SurfaceParams& params,
return surface_id;
}();
Surface& surface = slot_surfaces[surface_id];
if (params.res_scale > surface.res_scale) {
surface.ScaleUp(params.res_scale);
if ((params.res_scale > surface.res_scale) || (params.sample_count > surface.sample_count)) {
surface.ScaleUp(params.res_scale, params.sample_count);
}
surface.MarkInvalid(surface.GetInterval());
return surface_id;

View file

@ -227,6 +227,7 @@ private:
SurfaceMap dirty_regions;
PageMap cached_pages;
u32 resolution_scale_factor;
u8 sample_count;
u64 frame_tick{};
FramebufferParams fb_params;
Settings::TextureFilter filter;

View file

@ -219,12 +219,13 @@ u32 SurfaceParams::LevelOf(PAddr level_addr) const {
return level;
}
std::string SurfaceParams::DebugName(bool scaled, bool custom) const noexcept {
std::string SurfaceParams::DebugName(bool scaled, bool custom, u8 sample_count) const noexcept {
const u32 scaled_width = scaled ? GetScaledWidth() : width;
const u32 scaled_height = scaled ? GetScaledHeight() : height;
return fmt::format("Surface: {}x{} {} {} levels from {:#x} to {:#x} ({}{})", scaled_width,
scaled_height, PixelFormatAsString(pixel_format), levels, addr, end,
custom ? "custom," : "", scaled ? "scaled" : "unscaled");
return fmt::format("Surface: {}x{} {} samples {} levels from {:#x} to {:#x} ({}{})",
scaled_width, scaled_height, PixelFormatAsString(pixel_format),
static_cast<u32>(sample_count), levels, addr, end, custom ? "custom," : "",
scaled ? "scaled" : "unscaled");
}
bool SurfaceParams::operator==(const SurfaceParams& other) const noexcept {

View file

@ -51,7 +51,7 @@ public:
u32 LevelOf(PAddr addr) const;
/// Returns a string identifier of the params object
std::string DebugName(bool scaled, bool custom = false) const noexcept;
std::string DebugName(bool scaled, bool custom = false, u8 sample_count = 1) const noexcept;
bool operator==(const SurfaceParams& other) const noexcept;
@ -71,6 +71,10 @@ public:
return height * res_scale;
}
[[nodiscard]] u8 GetSampleCount() const noexcept {
return sample_count;
}
[[nodiscard]] Common::Rectangle<u32> GetRect(u32 level = 0) const noexcept {
return {0, height >> level, width >> level, 0};
}
@ -104,6 +108,7 @@ public:
u32 stride = 0;
u32 levels = 1;
u32 res_scale = 1;
u8 sample_count = 1;
bool is_tiled = false;
TextureType texture_type = TextureType::Texture2D;

View file

@ -556,23 +556,31 @@ void Surface::Attach(GLenum target, u32 level, u32 layer, bool scaled) {
}
}
void Surface::ScaleUp(u32 new_scale) {
if (res_scale == new_scale || new_scale == 1) {
void Surface::ScaleUp(u32 new_scale, u8 new_sample_count) {
if (res_scale == new_scale && sample_count == new_sample_count) {
return;
}
res_scale = new_scale;
textures[1] = MakeHandle(GL_TEXTURE_2D, GetScaledWidth(), GetScaledHeight(), levels, tuple,
DebugName(true));
sample_count = new_sample_count;
for (u32 level = 0; level < levels; level++) {
const VideoCore::TextureBlit blit = {
.src_level = level,
.dst_level = level,
.src_rect = GetRect(level),
.dst_rect = GetScaledRect(level),
};
BlitScale(blit, true);
if (res_scale > 1) {
textures[1] = MakeHandle(GL_TEXTURE_2D, GetScaledWidth(), GetScaledHeight(), levels, tuple,
DebugName(true));
for (u32 level = 0; level < levels; level++) {
const VideoCore::TextureBlit blit = {
.src_level = level,
.dst_level = level,
.src_rect = GetRect(level),
.dst_rect = GetScaledRect(level),
};
BlitScale(blit, true);
}
}
if (new_sample_count > 1) {
// Todo(wunk): OpenGL MSAA
}
}

View file

@ -132,8 +132,8 @@ public:
/// Attaches a handle of surface to the specified framebuffer target
void Attach(GLenum target, u32 level, u32 layer, bool scaled = true);
/// Scales up the surface to match the new resolution scale.
void ScaleUp(u32 new_scale);
/// Scales up the surface to match the new resolution scale and sample-count.
void ScaleUp(u32 new_scale, u8 new_sample_count);
/// Returns the bpp of the internal surface format
u32 GetInternalBytesPerPixel() const;

View file

@ -731,7 +731,8 @@ Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceParams& param
const VideoCore::SurfaceFlagBits& initial_flag_bits)
: SurfaceBase{params, initial_flag_bits}, runtime{runtime_}, instance{runtime_.GetInstance()},
scheduler{runtime_.GetScheduler()}, traits{instance.GetTraits(pixel_format)},
handles{Handle(instance), Handle(instance), Handle(instance), Handle(instance)} {
handles{Handle(instance), Handle(instance), Handle(instance), Handle(instance),
Handle(instance)} {
if (pixel_format == VideoCore::PixelFormat::Invalid || !traits.transfer_support) {
return;
@ -800,7 +801,8 @@ Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceBase& surface
const VideoCore::Material* mat)
: SurfaceBase{surface}, runtime{runtime_}, instance{runtime_.GetInstance()},
scheduler{runtime_.GetScheduler()}, traits{instance.GetTraits(mat->format)},
handles{Handle(instance), Handle(instance), Handle(instance), Handle(instance)} {
handles{Handle(instance), Handle(instance), Handle(instance), Handle(instance),
Handle(instance)} {
if (!traits.transfer_support) {
return;
}
@ -1096,12 +1098,13 @@ void Surface::Download(const VideoCore::BufferTextureCopy& download,
});
}
void Surface::ScaleUp(u32 new_scale) {
if (res_scale == new_scale || new_scale == 1) {
void Surface::ScaleUp(u32 new_scale, u8 new_sample_count) {
if (res_scale == new_scale && sample_count == new_sample_count) {
return;
}
res_scale = new_scale;
sample_count = new_sample_count;
const bool is_mutable = pixel_format == VideoCore::PixelFormat::RGBA8;
@ -1113,29 +1116,36 @@ void Surface::ScaleUp(u32 new_scale) {
flags |= vk::ImageCreateFlagBits::eMutableFormat;
}
handles[Type::Scaled].Create(GetScaledWidth(), GetScaledHeight(), levels, texture_type,
traits.native, traits.usage, flags, traits.aspect, false,
DebugName(true));
current = Type::Scaled;
if (res_scale > 1) {
runtime.renderpass_cache.EndRendering();
scheduler.Record(
[raw_images = std::array{Image()}, aspect = traits.aspect](vk::CommandBuffer cmdbuf) {
std::array<vk::ImageMemoryBarrier, 1> barriers;
MakeInitBarriers(aspect, 1, raw_images, barriers);
cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTopOfPipe,
vk::PipelineStageFlagBits::eTopOfPipe,
vk::DependencyFlagBits::eByRegion, {}, {}, barriers);
});
handles[Type::Scaled].Create(GetScaledWidth(), GetScaledHeight(), levels, texture_type,
traits.native, traits.usage, flags, traits.aspect, false,
DebugName(true));
current = Type::Scaled;
for (u32 level = 0; level < levels; level++) {
const VideoCore::TextureBlit blit = {
.src_level = level,
.dst_level = level,
.src_rect = GetRect(level),
.dst_rect = GetScaledRect(level),
};
BlitScale(blit, true);
runtime.renderpass_cache.EndRendering();
scheduler.Record(
[raw_images = std::array{Image()}, aspect = traits.aspect](vk::CommandBuffer cmdbuf) {
std::array<vk::ImageMemoryBarrier, 1> barriers;
MakeInitBarriers(aspect, 1, raw_images, barriers);
cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTopOfPipe,
vk::PipelineStageFlagBits::eTopOfPipe,
vk::DependencyFlagBits::eByRegion, {}, {}, barriers);
});
for (u32 level = 0; level < levels; level++) {
const VideoCore::TextureBlit blit = {
.src_level = level,
.dst_level = level,
.src_rect = GetRect(level),
.dst_rect = GetScaledRect(level),
};
BlitScale(blit, true);
}
}
if (new_sample_count > 1) {
// Todo(wunk): Vulkan MSAA
}
}

View file

@ -29,6 +29,7 @@ enum Type {
Current = -1,
Base = 0,
Scaled,
MultiSampled,
Custom,
Copy,
Num,
@ -252,8 +253,8 @@ public:
void Download(const VideoCore::BufferTextureCopy& download,
const VideoCore::StagingData& staging);
/// Scales up the surface to match the new resolution scale.
void ScaleUp(u32 new_scale);
/// Scales up the surface to match the new resolution scale and sample-count.
void ScaleUp(u32 new_scale, u8 new_sample_count);
/// Returns the bpp of the internal surface format
u32 GetInternalBytesPerPixel() const;