diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 773a5f325..d3a74755f 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -28,7 +28,8 @@ add_library(video_core STATIC regs_texturing.h renderer_base.cpp renderer_base.h - rasterizer_cache/texture_codec.h + rasterizer_cache/framebuffer_base.cpp + rasterizer_cache/framebuffer_base.h rasterizer_cache/pixel_format.cpp rasterizer_cache/pixel_format.h rasterizer_cache/rasterizer_cache.cpp @@ -36,8 +37,9 @@ add_library(video_core STATIC rasterizer_cache/rasterizer_cache_base.h rasterizer_cache/sampler_params.h rasterizer_cache/slot_vector.h - rasterizer_cache/surface_base.h rasterizer_cache/surface_base.cpp + rasterizer_cache/surface_base.h + rasterizer_cache/texture_codec.h rasterizer_cache/utils.cpp rasterizer_cache/utils.h rasterizer_cache/surface_params.cpp diff --git a/src/video_core/rasterizer_cache/framebuffer_base.cpp b/src/video_core/rasterizer_cache/framebuffer_base.cpp new file mode 100644 index 000000000..ce03b26d2 --- /dev/null +++ b/src/video_core/rasterizer_cache/framebuffer_base.cpp @@ -0,0 +1,64 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "video_core/rasterizer_cache/framebuffer_base.h" +#include "video_core/rasterizer_cache/surface_base.h" +#include "video_core/regs.h" + +namespace VideoCore { + +FramebufferBase::FramebufferBase() = default; + +FramebufferBase::FramebufferBase(const Pica::Regs& regs, SurfaceBase* const color_, + SurfaceBase* const depth_stencil_, + Common::Rectangle surfaces_rect) + : color{color_}, depth_stencil{depth_stencil_} { + res_scale = color ? color->res_scale : (depth_stencil ? depth_stencil->res_scale : 1u); + + // Determine the draw rectangle (render area + scissor) + const Common::Rectangle viewport_rect = regs.rasterizer.GetViewportRect(); + draw_rect.left = + std::clamp(static_cast(surfaces_rect.left) + viewport_rect.left * res_scale, + surfaces_rect.left, surfaces_rect.right); + draw_rect.top = + std::clamp(static_cast(surfaces_rect.bottom) + viewport_rect.top * res_scale, + surfaces_rect.bottom, surfaces_rect.top); + draw_rect.right = + std::clamp(static_cast(surfaces_rect.left) + viewport_rect.right * res_scale, + surfaces_rect.left, surfaces_rect.right); + draw_rect.bottom = + std::clamp(static_cast(surfaces_rect.bottom) + viewport_rect.bottom * res_scale, + surfaces_rect.bottom, surfaces_rect.top); + + // Update viewport + viewport.x = surfaces_rect.left + viewport_rect.left * res_scale; + viewport.y = surfaces_rect.bottom + viewport_rect.bottom * res_scale; + viewport.width = viewport_rect.GetWidth() * res_scale; + viewport.height = viewport_rect.GetHeight() * res_scale; + + // Scissor checks are window-, not viewport-relative, which means that if the cached texture + // sub-rect changes, the scissor bounds also need to be updated. + scissor_rect.left = + static_cast(surfaces_rect.left + regs.rasterizer.scissor_test.x1 * res_scale); + scissor_rect.bottom = + static_cast(surfaces_rect.bottom + regs.rasterizer.scissor_test.y1 * res_scale); + + // x2, y2 have +1 added to cover the entire pixel area, otherwise you might get cracks when + // scaling or doing multisampling. + scissor_rect.right = + static_cast(surfaces_rect.left + (regs.rasterizer.scissor_test.x2 + 1) * res_scale); + scissor_rect.top = + static_cast(surfaces_rect.bottom + (regs.rasterizer.scissor_test.y2 + 1) * res_scale); + + // Query surface invalidation intervals + const Common::Rectangle draw_rect_unscaled{draw_rect / res_scale}; + if (color) { + intervals[0] = color->GetSubRectInterval(draw_rect_unscaled); + } + if (depth_stencil) { + intervals[1] = depth_stencil->GetSubRectInterval(draw_rect_unscaled); + } +} + +} // namespace VideoCore diff --git a/src/video_core/rasterizer_cache/framebuffer_base.h b/src/video_core/rasterizer_cache/framebuffer_base.h new file mode 100644 index 000000000..4e27a0343 --- /dev/null +++ b/src/video_core/rasterizer_cache/framebuffer_base.h @@ -0,0 +1,86 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/math_util.h" +#include "video_core/rasterizer_cache/utils.h" + +namespace Pica { +struct Regs; +} + +namespace VideoCore { + +class SurfaceBase; + +struct ViewportInfo { + f32 x; + f32 y; + f32 width; + f32 height; +}; + +/** + * A framebuffer is a lightweight abstraction over a pair of surfaces and provides + * metadata about them. + */ +class FramebufferBase { +public: + FramebufferBase(); + FramebufferBase(const Pica::Regs& regs, SurfaceBase* const color, + SurfaceBase* const depth_stencil, Common::Rectangle surfaces_rect); + + SurfaceBase* Color() const noexcept { + return color; + } + + SurfaceBase* DepthStencil() const noexcept { + return depth_stencil; + } + + SurfaceInterval Interval(SurfaceType type) const noexcept { + return intervals[Index(type)]; + } + + u32 ResolutionScale() const noexcept { + return res_scale; + } + + Common::Rectangle DrawRect() const noexcept { + return draw_rect; + } + + Common::Rectangle Scissor() const noexcept { + return scissor_rect; + } + + ViewportInfo Viewport() const noexcept { + return viewport; + } + +protected: + u32 Index(VideoCore::SurfaceType type) const noexcept { + switch (type) { + case VideoCore::SurfaceType::Color: + return 0; + case VideoCore::SurfaceType::DepthStencil: + return 1; + default: + LOG_CRITICAL(Render_Vulkan, "Unknown surface type in framebuffer"); + return 0; + } + } + +protected: + SurfaceBase* color{}; + SurfaceBase* depth_stencil{}; + std::array intervals{}; + Common::Rectangle scissor_rect{}; + Common::Rectangle draw_rect{}; + ViewportInfo viewport; + u32 res_scale{1}; +}; + +} // namespace VideoCore diff --git a/src/video_core/rasterizer_cache/rasterizer_cache.h b/src/video_core/rasterizer_cache/rasterizer_cache.h index 84593f6fb..8546a64a9 100644 --- a/src/video_core/rasterizer_cache/rasterizer_cache.h +++ b/src/video_core/rasterizer_cache/rasterizer_cache.h @@ -707,32 +707,28 @@ auto RasterizerCache::GetTextureCube(const TextureCubeConfig& config) -> cons } template -auto RasterizerCache::GetFramebufferSurfaces(bool using_color_fb, bool using_depth_fb, - const Common::Rectangle& viewport_rect) - -> SurfaceSurfaceRect_Tuple { +auto RasterizerCache::GetFramebufferSurfaces(bool using_color_fb, bool using_depth_fb) + -> Framebuffer { const auto& regs = Pica::g_state.regs; const auto& config = regs.framebuffer.framebuffer; // Update resolution_scale_factor and reset cache if changed const bool resolution_scale_changed = resolution_scale_factor != VideoCore::GetResolutionScaleFactor(); - const bool texture_filter_changed = - /*VideoCore::g_texture_filter_update_requested.exchange(false) && - texture_filterer->Reset(Settings::values.texture_filter_name, - VideoCore::GetResolutionScaleFactor())*/ - false; - - if (resolution_scale_changed || texture_filter_changed) [[unlikely]] { + if (resolution_scale_changed) [[unlikely]] { resolution_scale_factor = VideoCore::GetResolutionScaleFactor(); UnregisterAll(); } - Common::Rectangle viewport_clamped{ - static_cast(std::clamp(viewport_rect.left, 0, static_cast(config.GetWidth()))), - static_cast(std::clamp(viewport_rect.top, 0, static_cast(config.GetHeight()))), - static_cast(std::clamp(viewport_rect.right, 0, static_cast(config.GetWidth()))), - static_cast( - std::clamp(viewport_rect.bottom, 0, static_cast(config.GetHeight())))}; + const s32 framebuffer_width = config.GetWidth(); + const s32 framebuffer_height = config.GetHeight(); + const auto viewport_rect = regs.rasterizer.GetViewportRect(); + const Common::Rectangle viewport_clamped = { + static_cast(std::clamp(viewport_rect.left, 0, framebuffer_width)), + static_cast(std::clamp(viewport_rect.top, 0, framebuffer_height)), + static_cast(std::clamp(viewport_rect.right, 0, framebuffer_width)), + static_cast(std::clamp(viewport_rect.bottom, 0, framebuffer_height)), + }; // get color and depth surfaces SurfaceParams color_params; @@ -756,25 +752,25 @@ auto RasterizerCache::GetFramebufferSurfaces(bool using_color_fb, bool using_ // Make sure that framebuffers don't overlap if both color and depth are being used if (using_color_fb && using_depth_fb && boost::icl::length(color_vp_interval & depth_vp_interval)) { - LOG_CRITICAL(Render_OpenGL, "Color and depth framebuffer memory regions overlap; " - "overlapping framebuffers not supported!"); + LOG_CRITICAL(HW_GPU, "Color and depth framebuffer memory regions overlap; " + "overlapping framebuffers not supported!"); using_depth_fb = false; } Common::Rectangle color_rect{}; - Surface color_surface = nullptr; + Surface color_surface{}; if (using_color_fb) std::tie(color_surface, color_rect) = GetSurfaceSubRect(color_params, ScaleMatch::Exact, false); Common::Rectangle depth_rect{}; - Surface depth_surface = nullptr; + Surface depth_surface{}; if (using_depth_fb) std::tie(depth_surface, depth_rect) = GetSurfaceSubRect(depth_params, ScaleMatch::Exact, false); Common::Rectangle fb_rect{}; - if (color_surface != nullptr && depth_surface != nullptr) { + if (color_surface && depth_surface) { fb_rect = color_rect; // Color and Depth surfaces must have the same dimensions and offsets if (color_rect.bottom != depth_rect.bottom || color_rect.top != depth_rect.top || @@ -783,24 +779,46 @@ auto RasterizerCache::GetFramebufferSurfaces(bool using_color_fb, bool using_ depth_surface = GetSurface(depth_params, ScaleMatch::Exact, false); fb_rect = color_surface->GetScaledRect(); } - } else if (color_surface != nullptr) { + } else if (color_surface) { fb_rect = color_rect; - } else if (depth_surface != nullptr) { + } else if (depth_surface) { fb_rect = depth_rect; } - if (color_surface != nullptr) { + if (color_surface) { ValidateSurface(color_surface, boost::icl::first(color_vp_interval), boost::icl::length(color_vp_interval)); color_surface->InvalidateAllWatcher(); } - if (depth_surface != nullptr) { + if (depth_surface) { ValidateSurface(depth_surface, boost::icl::first(depth_vp_interval), boost::icl::length(depth_vp_interval)); depth_surface->InvalidateAllWatcher(); } - return std::make_tuple(color_surface, depth_surface, fb_rect); + render_targets = RenderTargets{ + .color_surface = color_surface, + .depth_surface = depth_surface, + }; + + auto* const color = color_surface ? color_surface.get() : nullptr; + auto* const depth_stencil = depth_surface ? depth_surface.get() : nullptr; + return Framebuffer{runtime, color, depth_stencil, regs, fb_rect}; +} + +template +void RasterizerCache::InvalidateRenderTargets(const Framebuffer& framebuffer) { + const auto Invalidate = [&](SurfaceType type, Surface region_owner) { + const bool has_attachment = framebuffer.HasAttachment(type); + if (has_attachment) { + const SurfaceInterval interval = framebuffer.Interval(type); + InvalidateRegion(boost::icl::first(interval), boost::icl::length(interval), + region_owner); + } + }; + + Invalidate(SurfaceType::Color, render_targets.color_surface); + Invalidate(SurfaceType::DepthStencil, render_targets.depth_surface); } template diff --git a/src/video_core/rasterizer_cache/rasterizer_cache_base.h b/src/video_core/rasterizer_cache/rasterizer_cache_base.h index 5167018dc..b389577dc 100644 --- a/src/video_core/rasterizer_cache/rasterizer_cache_base.h +++ b/src/video_core/rasterizer_cache/rasterizer_cache_base.h @@ -45,8 +45,9 @@ class RasterizerCache : NonCopyable { static constexpr u64 CITRA_PAGEBITS = 18; using TextureRuntime = typename T::RuntimeType; - using Sampler = typename T::Sampler; using Surface = std::shared_ptr; + using Sampler = typename T::Sampler; + using Framebuffer = typename T::Framebuffer; /// Declare rasterizer interval types using SurfaceMap = boost::icl::interval_map; using SurfaceRect_Tuple = std::tuple>; - using SurfaceSurfaceRect_Tuple = std::tuple>; using PageMap = boost::icl::interval_map; public: @@ -95,8 +95,10 @@ public: const Surface& GetTextureCube(const TextureCubeConfig& config); /// Get the color and depth surfaces based on the framebuffer configuration - SurfaceSurfaceRect_Tuple GetFramebufferSurfaces(bool using_color_fb, bool using_depth_fb, - const Common::Rectangle& viewport_rect); + Framebuffer GetFramebufferSurfaces(bool using_color_fb, bool using_depth_fb); + + /// Marks the draw rectangle defined in framebuffer as invalid + void InvalidateRenderTargets(const Framebuffer& framebuffer); /// Get a surface that matches a "texture copy" display transfer config SurfaceRect_Tuple GetTexCopySurface(const SurfaceParams& params); @@ -196,6 +198,13 @@ private: std::unordered_map samplers; SlotVector slot_samplers; + + struct RenderTargets { + Surface color_surface; + Surface depth_surface; + }; + + RenderTargets render_targets; }; } // namespace VideoCore diff --git a/src/video_core/rasterizer_cache/surface_base.h b/src/video_core/rasterizer_cache/surface_base.h index f98ed2ccd..0595158ff 100644 --- a/src/video_core/rasterizer_cache/surface_base.h +++ b/src/video_core/rasterizer_cache/surface_base.h @@ -5,6 +5,7 @@ #pragma once #include +#include #include "video_core/rasterizer_cache/surface_params.h" namespace VideoCore { diff --git a/src/video_core/rasterizer_cache/surface_params.h b/src/video_core/rasterizer_cache/surface_params.h index 3c96b419f..ea43d44b3 100644 --- a/src/video_core/rasterizer_cache/surface_params.h +++ b/src/video_core/rasterizer_cache/surface_params.h @@ -4,13 +4,10 @@ #pragma once -#include #include "video_core/rasterizer_cache/utils.h" namespace VideoCore { -using SurfaceInterval = boost::icl::right_open_interval; - class SurfaceParams { public: /// Returns true if other_surface matches exactly params diff --git a/src/video_core/rasterizer_cache/utils.h b/src/video_core/rasterizer_cache/utils.h index ca63b81be..0bfd997c7 100644 --- a/src/video_core/rasterizer_cache/utils.h +++ b/src/video_core/rasterizer_cache/utils.h @@ -5,6 +5,7 @@ #pragma once #include +#include #include "common/hash.h" #include "common/math_util.h" #include "common/vector_math.h" @@ -20,6 +21,8 @@ using SamplerId = SlotId; /// Fake sampler ID for null samplers constexpr SamplerId NULL_SAMPLER_ID{0}; +using SurfaceInterval = boost::icl::right_open_interval; + struct Offset { constexpr auto operator<=>(const Offset&) const noexcept = default; diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 338a9d45a..3d967ed77 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -18,6 +18,8 @@ namespace OpenGL { +using VideoCore::SurfaceType; + constexpr std::size_t VERTEX_BUFFER_SIZE = 16 * 1024 * 1024; constexpr std::size_t INDEX_BUFFER_SIZE = 1 * 1024 * 1024; constexpr std::size_t UNIFORM_BUFFER_SIZE = 2 * 1024 * 1024; @@ -355,95 +357,36 @@ bool RasterizerOpenGL::Draw(bool accelerate, bool is_indexed) { (write_depth_fb || regs.framebuffer.output_merger.depth_test_enable != 0 || (has_stencil && state.stencil.test_enabled)); - const Common::Rectangle viewport_rect_unscaled = regs.rasterizer.GetViewportRect(); - - const auto [color_surface, depth_surface, surfaces_rect] = - res_cache.GetFramebufferSurfaces(using_color_fb, using_depth_fb, viewport_rect_unscaled); - - const u16 res_scale = color_surface != nullptr - ? color_surface->res_scale - : (depth_surface == nullptr ? 1u : depth_surface->res_scale); - - const Common::Rectangle draw_rect{ - static_cast(std::clamp(static_cast(surfaces_rect.left) + - viewport_rect_unscaled.left * res_scale, - surfaces_rect.left, surfaces_rect.right)), // Left - static_cast(std::clamp(static_cast(surfaces_rect.bottom) + - viewport_rect_unscaled.top * res_scale, - surfaces_rect.bottom, surfaces_rect.top)), // Top - static_cast(std::clamp(static_cast(surfaces_rect.left) + - viewport_rect_unscaled.right * res_scale, - surfaces_rect.left, surfaces_rect.right)), // Right - static_cast(std::clamp(static_cast(surfaces_rect.bottom) + - viewport_rect_unscaled.bottom * res_scale, - surfaces_rect.bottom, surfaces_rect.top))}; // Bottom - - // Bind the framebuffer surfaces - state.draw.draw_framebuffer = framebuffer.handle; - state.Apply(); - - if (shadow_rendering) { - if (color_surface == nullptr) { - return true; - } - - glFramebufferParameteri(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_WIDTH, - color_surface->width * color_surface->res_scale); - glFramebufferParameteri(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_HEIGHT, - color_surface->height * color_surface->res_scale); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, - 0); - state.image_shadow_buffer = color_surface->texture.handle; - } else { - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, - color_surface != nullptr ? color_surface->texture.handle : 0, 0); - if (depth_surface != nullptr) { - if (has_stencil) { - // attach both depth and stencil - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, - GL_TEXTURE_2D, depth_surface->texture.handle, 0); - } else { - // attach depth - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, - depth_surface->texture.handle, 0); - // clear stencil attachment - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, - 0); - } - } else { - // clear both depth and stencil attachment - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, - 0, 0); - } + const Framebuffer framebuffer = + res_cache.GetFramebufferSurfaces(using_color_fb, using_depth_fb); + const bool has_color = framebuffer.HasAttachment(SurfaceType::Color); + const bool has_depth_stencil = framebuffer.HasAttachment(SurfaceType::DepthStencil); + if (!has_color && (shadow_rendering || !has_depth_stencil)) { + return true; } - // Sync the viewport - state.viewport.x = - static_cast(surfaces_rect.left) + viewport_rect_unscaled.left * res_scale; - state.viewport.y = - static_cast(surfaces_rect.bottom) + viewport_rect_unscaled.bottom * res_scale; - state.viewport.width = static_cast(viewport_rect_unscaled.GetWidth() * res_scale); - state.viewport.height = static_cast(viewport_rect_unscaled.GetHeight() * res_scale); + // Bind the framebuffer surfaces + if (shadow_rendering) { + state.image_shadow_buffer = framebuffer.Attachment(SurfaceType::Color); + } + state.draw.draw_framebuffer = framebuffer.Handle(); + state.Apply(); + // Sync the viewport + const auto viewport = framebuffer.Viewport(); + state.viewport.x = viewport.x; + state.viewport.y = viewport.y; + state.viewport.width = viewport.width; + state.viewport.height = viewport.height; + + const u32 res_scale = framebuffer.ResolutionScale(); if (uniform_block_data.data.framebuffer_scale != res_scale) { uniform_block_data.data.framebuffer_scale = res_scale; uniform_block_data.dirty = true; } - // Scissor checks are window-, not viewport-relative, which means that if the cached texture - // sub-rect changes, the scissor bounds also need to be updated. - GLint scissor_x1 = - static_cast(surfaces_rect.left + regs.rasterizer.scissor_test.x1 * res_scale); - GLint scissor_y1 = - static_cast(surfaces_rect.bottom + regs.rasterizer.scissor_test.y1 * res_scale); - // x2, y2 have +1 added to cover the entire pixel area, otherwise you might get cracks when - // scaling or doing multisampling. - GLint scissor_x2 = - static_cast(surfaces_rect.left + (regs.rasterizer.scissor_test.x2 + 1) * res_scale); - GLint scissor_y2 = static_cast(surfaces_rect.bottom + - (regs.rasterizer.scissor_test.y2 + 1) * res_scale); - + // Update scissor uniforms + const auto [scissor_x1, scissor_y2, scissor_x2, scissor_y1] = framebuffer.Scissor(); if (uniform_block_data.data.scissor_x1 != scissor_x1 || uniform_block_data.data.scissor_x2 != scissor_x2 || uniform_block_data.data.scissor_y1 != scissor_y1 || @@ -456,20 +399,13 @@ bool RasterizerOpenGL::Draw(bool accelerate, bool is_indexed) { uniform_block_data.dirty = true; } - bool need_duplicate_texture = false; - auto CheckBarrier = [&need_duplicate_texture, &color_surface = color_surface](GLuint handle) { - if (color_surface && color_surface->texture.handle == handle) { - need_duplicate_texture = true; - } - }; - const auto BindCubeFace = [&](GLuint& target, Pica::TexturingRegs::CubeFace face, Pica::Texture::TextureInfo& info) { info.physical_address = regs.texturing.GetCubePhysicalAddress(face); auto surface = res_cache.GetTextureSurface(info); if (surface != nullptr) { - CheckBarrier(target = surface->texture.handle); + target = surface->texture.handle; } else { target = 0; } @@ -488,7 +424,7 @@ bool RasterizerOpenGL::Draw(bool accelerate, bool is_indexed) { case TextureType::Shadow2D: { const auto surface = res_cache.GetTextureSurface(texture); if (surface) { - CheckBarrier(state.image_shadow_texture_px = surface->texture.handle); + state.image_shadow_texture_px = surface->texture.handle; } else { state.image_shadow_texture_px = 0; } @@ -536,8 +472,7 @@ bool RasterizerOpenGL::Draw(bool accelerate, bool is_indexed) { auto surface = res_cache.GetTextureSurface(texture); if (surface != nullptr) { - CheckBarrier(state.texture_units[texture_index].texture_2d = - surface->texture.handle); + state.texture_units[texture_index].texture_2d = surface->texture.handle; } else { // Can occur when texture addr is null or its memory is unmapped/invalid // HACK: In this case, the correct behaviour for the PICA is to use the last @@ -553,36 +488,6 @@ bool RasterizerOpenGL::Draw(bool accelerate, bool is_indexed) { } } - // The game is trying to use a surface as a texture and framebuffer at the same time - // which causes unpredictable behavior on the host. - // Making a copy to sample from eliminates this issue and seems to be fairly cheap. - if (need_duplicate_texture) { - Surface temp{*color_surface, runtime}; - const VideoCore::TextureCopy copy = { - .src_level = 0, - .dst_level = 0, - .src_layer = 0, - .dst_layer = 0, - .src_offset = {0, 0}, - .dst_offset = {0, 0}, - .extent = {temp.GetScaledWidth(), temp.GetScaledHeight()}, - }; - runtime.CopyTextures(*color_surface, temp, copy); - - for (auto& unit : state.texture_units) { - if (unit.texture_2d == color_surface->texture.handle) { - unit.texture_2d = temp.Handle(); - } - } - for (auto shadow_unit : {&state.image_shadow_texture_nx, &state.image_shadow_texture_ny, - &state.image_shadow_texture_nz, &state.image_shadow_texture_px, - &state.image_shadow_texture_py, &state.image_shadow_texture_pz}) { - if (*shadow_unit == color_surface->texture.handle) { - *shadow_unit = temp.Handle(); - } - } - } - // Sync and bind the shader if (shader_dirty) { SetShader(); @@ -596,10 +501,9 @@ bool RasterizerOpenGL::Draw(bool accelerate, bool is_indexed) { // Sync the uniform data UploadUniforms(accelerate); - // Viewport can have negative offsets or larger - // dimensions than our framebuffer sub-rect. - // Enable scissor test to prevent drawing - // outside of the framebuffer region + // Viewport can have negative offsets or larger dimensions than our framebuffer sub-rect. + // Enable scissor test to prevent drawing outside of the framebuffer region + const auto draw_rect = framebuffer.DrawRect(); state.scissor.enabled = true; state.scissor.x = draw_rect.left; state.scissor.y = draw_rect.bottom; @@ -636,37 +540,13 @@ bool RasterizerOpenGL::Draw(bool accelerate, bool is_indexed) { vertex_batch.clear(); - // Reset textures in rasterizer state context because the rasterizer cache might delete them - for (u32 texture_index = 0; texture_index < pica_textures.size(); ++texture_index) { - state.texture_units[texture_index].texture_2d = 0; - } - state.texture_cube_unit.texture_cube = 0; - state.image_shadow_texture_px = 0; - state.image_shadow_texture_nx = 0; - state.image_shadow_texture_py = 0; - state.image_shadow_texture_ny = 0; - state.image_shadow_texture_pz = 0; - state.image_shadow_texture_nz = 0; - state.image_shadow_buffer = 0; - state.Apply(); - if (shadow_rendering) { glMemoryBarrier(GL_TEXTURE_FETCH_BARRIER_BIT | GL_SHADER_IMAGE_ACCESS_BARRIER_BIT | GL_TEXTURE_UPDATE_BARRIER_BIT | GL_FRAMEBUFFER_BARRIER_BIT); } // Mark framebuffer surfaces as dirty - const Common::Rectangle draw_rect_unscaled{draw_rect / res_scale}; - if (color_surface && write_color_fb) { - auto interval = color_surface->GetSubRectInterval(draw_rect_unscaled); - res_cache.InvalidateRegion(boost::icl::first(interval), boost::icl::length(interval), - color_surface); - } - if (depth_surface && write_depth_fb) { - auto interval = depth_surface->GetSubRectInterval(draw_rect_unscaled); - res_cache.InvalidateRegion(boost::icl::first(interval), boost::icl::length(interval), - depth_surface); - } + res_cache.InvalidateRenderTargets(framebuffer); return succeeded; } diff --git a/src/video_core/renderer_opengl/gl_texture_runtime.cpp b/src/video_core/renderer_opengl/gl_texture_runtime.cpp index b143d3e9c..342ad3199 100644 --- a/src/video_core/renderer_opengl/gl_texture_runtime.cpp +++ b/src/video_core/renderer_opengl/gl_texture_runtime.cpp @@ -6,6 +6,7 @@ #include "common/scope_exit.h" #include "common/settings.h" #include "video_core/rasterizer_cache/utils.h" +#include "video_core/regs.h" #include "video_core/renderer_opengl/gl_driver.h" #include "video_core/renderer_opengl/gl_format_reinterpreter.h" #include "video_core/renderer_opengl/gl_state.h" @@ -473,6 +474,74 @@ void Surface::ScaledDownload(const VideoCore::BufferTextureCopy& download, } } +Framebuffer::Framebuffer(TextureRuntime& runtime, Surface* const color, + Surface* const depth_stencil, const Pica::Regs& regs, + Common::Rectangle surfaces_rect) + : VideoCore::FramebufferBase{regs, color, depth_stencil, surfaces_rect} { + + const bool shadow_rendering = regs.framebuffer.IsShadowRendering(); + const bool has_stencil = regs.framebuffer.HasStencil(); + if (shadow_rendering && !color) { + return; // Framebuffer won't get used + } + + if (color) { + attachments[0] = color->Handle(); + } + if (depth_stencil) { + attachments[1] = depth_stencil->Handle(); + } + + const u64 hash = Common::ComputeStructHash64(attachments); + auto [it, new_framebuffer] = runtime.framebuffer_cache.try_emplace(hash); + if (!new_framebuffer) { + handle = it->second.handle; + return; + } + + // Create a new framebuffer otherwise + OGLFramebuffer& framebuffer = it->second; + framebuffer.Create(); + + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer.handle); + + if (shadow_rendering) { + glFramebufferParameteri(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_WIDTH, + color->width * res_scale); + glFramebufferParameteri(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_HEIGHT, + color->height * res_scale); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, + 0); + } else { + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, + color ? color->Handle() : 0, 0); + if (depth_stencil) { + if (has_stencil) { + // Attach both depth and stencil + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, + GL_TEXTURE_2D, depth_stencil->Handle(), 0); + } else { + // Attach depth + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, + depth_stencil->Handle(), 0); + // Clear stencil attachment + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, + 0); + } + } else { + // Clear both depth and stencil attachment + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, + 0, 0); + } + } + + // Restore previous framebuffer + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, OpenGLState::GetCurState().draw.draw_framebuffer); +} + +Framebuffer::~Framebuffer() = default; + Sampler::Sampler(TextureRuntime& runtime, VideoCore::SamplerParams params) { const GLenum mag_filter = PicaToGL::TextureMagFilterMode(params.min_filter); const GLenum min_filter = PicaToGL::TextureMinFilterMode(params.min_filter, params.mip_filter); diff --git a/src/video_core/renderer_opengl/gl_texture_runtime.h b/src/video_core/renderer_opengl/gl_texture_runtime.h index e2c9a332f..feddd74a0 100644 --- a/src/video_core/renderer_opengl/gl_texture_runtime.h +++ b/src/video_core/renderer_opengl/gl_texture_runtime.h @@ -6,6 +6,7 @@ #include #include +#include "video_core/rasterizer_cache/framebuffer_base.h" #include "video_core/rasterizer_cache/rasterizer_cache_base.h" #include "video_core/rasterizer_cache/surface_base.h" #include "video_core/renderer_opengl/gl_format_reinterpreter.h" @@ -36,6 +37,7 @@ class Surface; */ class TextureRuntime { friend class Surface; + friend class Framebuffer; public: TextureRuntime(Driver& driver); @@ -94,6 +96,7 @@ private: TextureFilterer filterer; std::array reinterpreters; std::unordered_multimap texture_recycler; + std::unordered_map> framebuffer_cache; StreamBuffer upload_buffer; std::vector download_buffer; OGLFramebuffer read_fbo, draw_fbo; @@ -135,6 +138,30 @@ public: OGLTexture texture{}; }; +class Framebuffer : public VideoCore::FramebufferBase { +public: + explicit Framebuffer(TextureRuntime& runtime, Surface* const color, + Surface* const depth_stencil, const Pica::Regs& regs, + Common::Rectangle surfaces_rect); + ~Framebuffer(); + + [[nodiscard]] GLuint Handle() const noexcept { + return handle; + } + + [[nodiscard]] GLuint Attachment(VideoCore::SurfaceType type) const noexcept { + return attachments[Index(type)]; + } + + bool HasAttachment(VideoCore::SurfaceType type) const noexcept { + return static_cast(attachments[Index(type)]); + } + +private: + std::array attachments{}; + GLuint handle{}; +}; + class Sampler { public: explicit Sampler(TextureRuntime& runtime, VideoCore::SamplerParams params); @@ -158,6 +185,7 @@ struct Traits { using RuntimeType = TextureRuntime; using SurfaceType = Surface; using Sampler = Sampler; + using Framebuffer = Framebuffer; }; using RasterizerCache = VideoCore::RasterizerCache; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 970e63f4a..11c9e9b60 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -680,16 +680,6 @@ void PipelineCache::BindSampler(u32 binding, vk::Sampler sampler) { desc_manager.SetBinding(2, binding, data); } -void PipelineCache::SetViewport(float x, float y, float width, float height) { - const vk::Viewport viewport{x, y, width, height, 0.f, 1.f}; - scheduler.Record([viewport](vk::CommandBuffer cmdbuf) { cmdbuf.setViewport(0, viewport); }); -} - -void PipelineCache::SetScissor(s32 x, s32 y, u32 width, u32 height) { - const vk::Rect2D scissor{{x, y}, {width, height}}; - scheduler.Record([scissor](vk::CommandBuffer cmdbuf) { cmdbuf.setScissor(0, scissor); }); -} - void PipelineCache::ApplyDynamic(const PipelineInfo& info, bool is_dirty) { scheduler.Record([is_dirty, current_dynamic = current_info.dynamic, dynamic = info.dynamic](vk::CommandBuffer cmdbuf) { diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.h b/src/video_core/renderer_vulkan/vk_pipeline_cache.h index 1ccd5cd6a..41933ebb4 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.h +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.h @@ -218,12 +218,6 @@ public: /// Binds a sampler to the specified binding void BindSampler(u32 binding, vk::Sampler sampler); - /// Sets the viewport rectangle to the provided values - void SetViewport(float x, float y, float width, float height); - - /// Sets the scissor rectange to the provided values - void SetScissor(s32 x, s32 y, u32 width, u32 height); - private: /// Applies dynamic pipeline state to the current command buffer void ApplyDynamic(const PipelineInfo& info, bool is_dirty); diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 25d99a7b8..c4152643c 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -25,6 +25,7 @@ MICROPROFILE_DEFINE(Vulkan_Blits, "Vulkan", "Blits", MP_RGB(100, 100, 255)); namespace Vulkan { using TriangleTopology = Pica::PipelineRegs::TriangleTopology; +using VideoCore::SurfaceType; constexpr u64 STREAM_BUFFER_SIZE = 128 * 1024 * 1024; constexpr u64 TEXTURE_BUFFER_SIZE = 2 * 1024 * 1024; @@ -437,7 +438,6 @@ void RasterizerVulkan::DrawTriangles() { return; } - const u64 vertex_size = vertex_batch.size() * sizeof(HardwareVertex); pipeline_info.rasterization.topology.Assign(Pica::PipelineRegs::TriangleTopology::List); pipeline_info.vertex_layout = software_layout; @@ -462,57 +462,25 @@ bool RasterizerVulkan::Draw(bool accelerate, bool is_indexed) { (write_depth_fb || regs.framebuffer.output_merger.depth_test_enable != 0 || (has_stencil && pipeline_info.depth_stencil.stencil_test_enable)); - const Common::Rectangle viewport_rect_unscaled = regs.rasterizer.GetViewportRect(); - - const auto [color_surface, depth_surface, surfaces_rect] = - res_cache.GetFramebufferSurfaces(using_color_fb, using_depth_fb, viewport_rect_unscaled); - - if (!color_surface && (shadow_rendering || !depth_surface)) { + const Framebuffer framebuffer = + res_cache.GetFramebufferSurfaces(using_color_fb, using_depth_fb); + const bool has_color = framebuffer.HasAttachment(SurfaceType::Color); + const bool has_depth_stencil = framebuffer.HasAttachment(SurfaceType::DepthStencil); + if (!has_color && (shadow_rendering || !has_depth_stencil)) { return true; } - pipeline_info.attachments.color_format = - color_surface ? color_surface->pixel_format : VideoCore::PixelFormat::Invalid; - pipeline_info.attachments.depth_format = - depth_surface ? depth_surface->pixel_format : VideoCore::PixelFormat::Invalid; - - const u16 res_scale = color_surface != nullptr - ? color_surface->res_scale - : (depth_surface == nullptr ? 1u : depth_surface->res_scale); - - const VideoCore::Rect2D draw_rect = { - static_cast(std::clamp(static_cast(surfaces_rect.left) + - viewport_rect_unscaled.left * res_scale, - surfaces_rect.left, surfaces_rect.right)), // Left - static_cast(std::clamp(static_cast(surfaces_rect.bottom) + - viewport_rect_unscaled.top * res_scale, - surfaces_rect.bottom, surfaces_rect.top)), // Top - static_cast(std::clamp(static_cast(surfaces_rect.left) + - viewport_rect_unscaled.right * res_scale, - surfaces_rect.left, surfaces_rect.right)), // Right - static_cast(std::clamp(static_cast(surfaces_rect.bottom) + - viewport_rect_unscaled.bottom * res_scale, - surfaces_rect.bottom, surfaces_rect.top))}; + pipeline_info.attachments.color_format = framebuffer.Format(SurfaceType::Color); + pipeline_info.attachments.depth_format = framebuffer.Format(SurfaceType::DepthStencil); + const u32 res_scale = framebuffer.ResolutionScale(); if (uniform_block_data.data.framebuffer_scale != res_scale) { uniform_block_data.data.framebuffer_scale = res_scale; uniform_block_data.dirty = true; } - // Scissor checks are window-, not viewport-relative, which means that if the cached texture - // sub-rect changes, the scissor bounds also need to be updated. - int scissor_x1 = - static_cast(surfaces_rect.left + regs.rasterizer.scissor_test.x1 * res_scale); - int scissor_y1 = - static_cast(surfaces_rect.bottom + regs.rasterizer.scissor_test.y1 * res_scale); - - // x2, y2 have +1 added to cover the entire pixel area, otherwise you might get cracks when - // scaling or doing multisampling. - int scissor_x2 = - static_cast(surfaces_rect.left + (regs.rasterizer.scissor_test.x2 + 1) * res_scale); - int scissor_y2 = - static_cast(surfaces_rect.bottom + (regs.rasterizer.scissor_test.y2 + 1) * res_scale); - + // Update scissor uniforms + const auto [scissor_x1, scissor_y2, scissor_x2, scissor_y1] = framebuffer.Scissor(); if (uniform_block_data.data.scissor_x1 != scissor_x1 || uniform_block_data.data.scissor_x2 != scissor_x2 || uniform_block_data.data.scissor_y1 != scissor_y1 || @@ -527,14 +495,8 @@ bool RasterizerVulkan::Draw(bool accelerate, bool is_indexed) { // Sync and bind the texture surfaces // NOTE: From here onwards its a safe zone to set the draw state, doing that any earlier will - // cause issues as the rasterizer cache might cause a scheduler switch and invalidate our state - SyncTextureUnits(color_surface.get()); - - const vk::Rect2D render_area = { - .offset{static_cast(draw_rect.left), static_cast(draw_rect.bottom)}, - .extent{draw_rect.GetWidth(), draw_rect.GetHeight()}, - }; - renderpass_cache.EnterRenderpass(color_surface.get(), depth_surface.get(), render_area); + // cause issues as the rasterizer cache might cause a scheduler flush and invalidate our state + SyncTextureUnits(framebuffer); // Sync and bind the shader if (shader_dirty) { @@ -549,16 +511,26 @@ bool RasterizerVulkan::Draw(bool accelerate, bool is_indexed) { // Sync the uniform data UploadUniforms(accelerate); - // Sync the viewport - pipeline_cache.SetViewport(surfaces_rect.left + viewport_rect_unscaled.left * res_scale, - surfaces_rect.bottom + viewport_rect_unscaled.bottom * res_scale, - viewport_rect_unscaled.GetWidth() * res_scale, - viewport_rect_unscaled.GetHeight() * res_scale); + // Begin the renderpass + renderpass_cache.EnterRenderpass(framebuffer); + // Sync the viewport // Viewport can have negative offsets or larger dimensions than our framebuffer sub-rect. // Enable scissor test to prevent drawing outside of the framebuffer region - pipeline_cache.SetScissor(draw_rect.left, draw_rect.bottom, draw_rect.GetWidth(), - draw_rect.GetHeight()); + scheduler.Record([viewport = framebuffer.Viewport(), + scissor = framebuffer.RenderArea()](vk::CommandBuffer cmdbuf) { + const vk::Viewport vk_viewport = { + .x = viewport.x, + .y = viewport.y, + .width = viewport.width, + .height = viewport.height, + .minDepth = 0.f, + .maxDepth = 1.f, + }; + + cmdbuf.setViewport(0, vk_viewport); + cmdbuf.setScissor(0, scissor); + }); // Draw the vertex batch bool succeeded = true; @@ -583,18 +555,7 @@ bool RasterizerVulkan::Draw(bool accelerate, bool is_indexed) { vertex_batch.clear(); // Mark framebuffer surfaces as dirty - const Common::Rectangle draw_rect_unscaled{draw_rect / res_scale}; - if (color_surface && write_color_fb) { - auto interval = color_surface->GetSubRectInterval(draw_rect_unscaled); - res_cache.InvalidateRegion(boost::icl::first(interval), boost::icl::length(interval), - color_surface); - } - - if (depth_surface && write_depth_fb) { - auto interval = depth_surface->GetSubRectInterval(draw_rect_unscaled); - res_cache.InvalidateRegion(boost::icl::first(interval), boost::icl::length(interval), - depth_surface); - } + res_cache.InvalidateRenderTargets(framebuffer); static int counter = 20; counter--; @@ -606,8 +567,10 @@ bool RasterizerVulkan::Draw(bool accelerate, bool is_indexed) { return succeeded; } -void RasterizerVulkan::SyncTextureUnits(Surface* const color_surface) { +void RasterizerVulkan::SyncTextureUnits(const Framebuffer& framebuffer) { const auto pica_textures = regs.texturing.GetTextures(); + const vk::ImageView color_view = framebuffer.ImageView(SurfaceType::Color); + for (u32 texture_index = 0; texture_index < pica_textures.size(); ++texture_index) { const auto& texture = pica_textures[texture_index]; @@ -676,8 +639,8 @@ void RasterizerVulkan::SyncTextureUnits(Surface* const color_surface) { auto surface = res_cache.GetTextureSurface(texture); if (surface) { - if (color_surface && color_surface->ImageView() == surface->ImageView()) { - Surface temp{*color_surface, runtime}; + if (color_view == surface->ImageView()) { + Surface temp{*framebuffer.Color(), runtime}; const VideoCore::TextureCopy copy = { .src_level = 0, .dst_level = 0, @@ -687,7 +650,7 @@ void RasterizerVulkan::SyncTextureUnits(Surface* const color_surface) { .dst_offset = {0, 0}, .extent = {temp.GetScaledWidth(), temp.GetScaledHeight()}, }; - runtime.CopyTextures(*color_surface, temp, copy); + runtime.CopyTextures(static_cast(*framebuffer.Color()), temp, copy); pipeline_cache.BindTexture(texture_index, temp.ImageView()); } else { pipeline_cache.BindTexture(texture_index, surface->ImageView()); diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h index a75355e04..f75d418e9 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.h +++ b/src/video_core/renderer_vulkan/vk_rasterizer.h @@ -93,7 +93,7 @@ private: void SyncAndUploadLUTsLF(); /// Syncs all enabled PICA texture units - void SyncTextureUnits(Surface* const color_surface); + void SyncTextureUnits(const Framebuffer& framebuffer); /// Upload the uniform blocks to the uniform buffer object void UploadUniforms(bool accelerate_draw); diff --git a/src/video_core/renderer_vulkan/vk_renderpass_cache.cpp b/src/video_core/renderer_vulkan/vk_renderpass_cache.cpp index 6cfabf3d1..70964a124 100644 --- a/src/video_core/renderer_vulkan/vk_renderpass_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_renderpass_cache.cpp @@ -11,6 +11,9 @@ namespace Vulkan { +using VideoCore::PixelFormat; +using VideoCore::SurfaceType; + RenderpassCache::RenderpassCache(const Instance& instance, Scheduler& scheduler) : instance{instance}, scheduler{scheduler}, dynamic_rendering{ instance.IsDynamicRenderingSupported()} {} @@ -38,66 +41,50 @@ RenderpassCache::~RenderpassCache() { void RenderpassCache::EnterRenderpass(Surface* const color, Surface* const depth_stencil, vk::Rect2D render_area, bool do_clear, vk::ClearValue clear) { - ASSERT(color || depth_stencil); + return EnterRenderpass(Framebuffer{color, depth_stencil, render_area}, do_clear, clear); +} +void RenderpassCache::EnterRenderpass(const Framebuffer& framebuffer, bool do_clear, + vk::ClearValue clear) { if (dynamic_rendering) { - return BeginRendering(color, depth_stencil, render_area, do_clear, clear); + return BeginRendering(framebuffer, do_clear, clear); } - u32 width = UINT32_MAX; - u32 height = UINT32_MAX; - u32 cursor = 0; - std::array formats{}; - std::array images{}; - std::array views{}; - - const auto Prepare = [&](Surface* const surface) { - if (!surface) { - formats[cursor++] = VideoCore::PixelFormat::Invalid; - return; - } - - width = std::min(width, surface->GetScaledWidth()); - height = std::min(height, surface->GetScaledHeight()); - formats[cursor] = surface->pixel_format; - images[cursor] = surface->Image(); - views[cursor++] = surface->FramebufferView(); - }; - - Prepare(color); - Prepare(depth_stencil); - - const RenderingInfo new_info = { - .color = - RenderTarget{ - .aspect = vk::ImageAspectFlagBits::eColor, - .image = images[0], - .image_view = views[0], - }, - .depth = - RenderTarget{ - .aspect = depth_stencil ? depth_stencil->Aspect() : vk::ImageAspectFlagBits::eDepth, - .image = images[1], - .image_view = views[1], - }, - .render_area = render_area, + RenderingInfo new_info = { + .color{ + .aspect = vk::ImageAspectFlagBits::eColor, + .image = framebuffer.Image(SurfaceType::Color), + .image_view = framebuffer.ImageView(SurfaceType::Color), + }, + .depth{ + .aspect = vk::ImageAspectFlagBits::eDepth, + .image = framebuffer.Image(SurfaceType::DepthStencil), + .image_view = framebuffer.ImageView(SurfaceType::Color), + }, + .render_area = framebuffer.RenderArea(), .clear = clear, .do_clear = do_clear, }; + const PixelFormat color_format = framebuffer.Format(SurfaceType::Color); + const PixelFormat depth_format = framebuffer.Format(SurfaceType::DepthStencil); + if (depth_format == PixelFormat::D24S8) { + new_info.depth.aspect |= vk::ImageAspectFlagBits::eStencil; + } + const bool is_dirty = scheduler.IsStateDirty(StateFlags::Renderpass); if (info == new_info && rendering && !is_dirty) { cmd_count++; return; } - const vk::RenderPass renderpass = GetRenderpass(formats[0], formats[1], do_clear); + const vk::RenderPass renderpass = GetRenderpass(color_format, depth_format, do_clear); const FramebufferInfo framebuffer_info = { - .color = views[0], - .depth = views[1], - .width = width, - .height = height, + .color = new_info.color.image_view, + .depth = new_info.depth.image_view, + .width = framebuffer.Width(), + .height = framebuffer.Height(), }; auto [it, new_framebuffer] = framebuffers.try_emplace(framebuffer_info); @@ -108,18 +95,18 @@ void RenderpassCache::EnterRenderpass(Surface* const color, Surface* const depth if (rendering) { ExitRenderpass(); } - scheduler.Record( - [render_area, clear, renderpass, framebuffer = it->second](vk::CommandBuffer cmdbuf) { - const vk::RenderPassBeginInfo renderpass_begin_info = { - .renderPass = renderpass, - .framebuffer = framebuffer, - .renderArea = render_area, - .clearValueCount = 1, - .pClearValues = &clear, - }; + scheduler.Record([render_area = new_info.render_area, clear, renderpass, + framebuffer = it->second](vk::CommandBuffer cmdbuf) { + const vk::RenderPassBeginInfo renderpass_begin_info = { + .renderPass = renderpass, + .framebuffer = framebuffer, + .renderArea = render_area, + .clearValueCount = 1, + .pClearValues = &clear, + }; - cmdbuf.beginRenderPass(renderpass_begin_info, vk::SubpassContents::eInline); - }); + cmdbuf.beginRenderPass(renderpass_begin_info, vk::SubpassContents::eInline); + }); scheduler.MarkStateNonDirty(StateFlags::Renderpass); info = new_info; @@ -200,27 +187,27 @@ void RenderpassCache::ExitRenderpass() { } } -void RenderpassCache::BeginRendering(Surface* const color, Surface* const depth_stencil, - vk::Rect2D render_area, bool do_clear, vk::ClearValue clear) { +void RenderpassCache::BeginRendering(const Framebuffer& framebuffer, bool do_clear, + vk::ClearValue clear) { RenderingInfo new_info = { - .render_area = render_area, + .color{ + .aspect = vk::ImageAspectFlagBits::eColor, + .image = framebuffer.Image(SurfaceType::Color), + .image_view = framebuffer.ImageView(SurfaceType::Color), + }, + .depth{ + .aspect = vk::ImageAspectFlagBits::eDepth, + .image = framebuffer.Image(SurfaceType::DepthStencil), + .image_view = framebuffer.ImageView(SurfaceType::DepthStencil), + }, + .render_area = framebuffer.RenderArea(), .clear = clear, .do_clear = do_clear, }; - if (color) { - new_info.color = RenderTarget{ - .aspect = vk::ImageAspectFlagBits::eColor, - .image = color->Image(), - .image_view = color->FramebufferView(), - }; - } - if (depth_stencil) { - new_info.depth = RenderTarget{ - .aspect = depth_stencil->Aspect(), - .image = depth_stencil->Image(), - .image_view = depth_stencil->FramebufferView(), - }; + const bool has_stencil = framebuffer.Format(SurfaceType::DepthStencil) == PixelFormat::D24S8; + if (has_stencil) { + new_info.depth.aspect |= vk::ImageAspectFlagBits::eStencil; } const bool is_dirty = scheduler.IsStateDirty(StateFlags::Renderpass); @@ -229,9 +216,6 @@ void RenderpassCache::BeginRendering(Surface* const color, Surface* const depth_ return; } - const bool has_stencil = - depth_stencil && depth_stencil->type == VideoCore::SurfaceType::DepthStencil; - if (rendering) { ExitRenderpass(); } diff --git a/src/video_core/renderer_vulkan/vk_renderpass_cache.h b/src/video_core/renderer_vulkan/vk_renderpass_cache.h index 215991c44..c15d0fa79 100644 --- a/src/video_core/renderer_vulkan/vk_renderpass_cache.h +++ b/src/video_core/renderer_vulkan/vk_renderpass_cache.h @@ -39,6 +39,8 @@ struct hash { namespace Vulkan { +class Framebuffer; + class RenderpassCache { static constexpr std::size_t MAX_COLOR_FORMATS = 5; static constexpr std::size_t MAX_DEPTH_FORMATS = 4; @@ -48,6 +50,8 @@ public: ~RenderpassCache(); /// Begins a new renderpass only when no other renderpass is currently active + void EnterRenderpass(const Framebuffer& framebuffer, bool do_clear = false, + vk::ClearValue clear = {}); void EnterRenderpass(Surface* const color, Surface* const depth_stencil, vk::Rect2D render_area, bool do_clear = false, vk::ClearValue clear = {}); @@ -68,8 +72,7 @@ public: private: /// Begins a new rendering scope using dynamic rendering - void BeginRendering(Surface* const color, Surface* const depth_stencil, vk::Rect2D render_area, - bool do_clear, vk::ClearValue clear); + void BeginRendering(const Framebuffer& framebuffer, bool do_clear, vk::ClearValue clear); /// Creates a renderpass configured appropriately and stores it in cached_renderpasses vk::RenderPass CreateRenderPass(vk::Format color, vk::Format depth, diff --git a/src/video_core/renderer_vulkan/vk_texture_runtime.cpp b/src/video_core/renderer_vulkan/vk_texture_runtime.cpp index 5f75db61f..8f16c420b 100644 --- a/src/video_core/renderer_vulkan/vk_texture_runtime.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_runtime.cpp @@ -1227,6 +1227,50 @@ void Surface::DepthStencilDownload(const VideoCore::BufferTextureCopy& download, r32_surface.Download(r32_download, staging); } +Framebuffer::Framebuffer(Surface* const color, Surface* const depth_stencil, + vk::Rect2D render_area_) + : render_area{render_area_} { + PrepareImages(color, depth_stencil); +} + +Framebuffer::Framebuffer(TextureRuntime& runtime, Surface* const color, + Surface* const depth_stencil, const Pica::Regs& regs, + Common::Rectangle surfaces_rect) + : VideoCore::FramebufferBase{regs, color, depth_stencil, surfaces_rect} { + + // Update render area + render_area.offset.x = draw_rect.left; + render_area.offset.y = draw_rect.bottom; + render_area.extent.width = draw_rect.GetWidth(); + render_area.extent.height = draw_rect.GetHeight(); + + PrepareImages(color, depth_stencil); +} + +Framebuffer::~Framebuffer() = default; + +void Framebuffer::PrepareImages(Surface* const color, Surface* const depth_stencil) { + u32 cursor{0}; + width = height = std::numeric_limits::max(); + + const auto Prepare = [&](Surface* const surface) { + if (!surface) { + formats[cursor++] = VideoCore::PixelFormat::Invalid; + return; + } + + width = std::min(width, surface->GetScaledWidth()); + height = std::min(height, surface->GetScaledHeight()); + formats[cursor] = surface->pixel_format; + images[cursor] = surface->Image(); + image_views[cursor++] = surface->ImageView(); + }; + + // Setup image handles + Prepare(color); + Prepare(depth_stencil); +} + Sampler::Sampler(TextureRuntime& runtime, VideoCore::SamplerParams params) : device{runtime.GetInstance().GetDevice()} { using TextureConfig = VideoCore::SamplerParams::TextureConfig; diff --git a/src/video_core/renderer_vulkan/vk_texture_runtime.h b/src/video_core/renderer_vulkan/vk_texture_runtime.h index 226c655cf..5a60f50ff 100644 --- a/src/video_core/renderer_vulkan/vk_texture_runtime.h +++ b/src/video_core/renderer_vulkan/vk_texture_runtime.h @@ -7,6 +7,7 @@ #include #include #include +#include "video_core/rasterizer_cache/framebuffer_base.h" #include "video_core/rasterizer_cache/rasterizer_cache_base.h" #include "video_core/rasterizer_cache/surface_base.h" #include "video_core/renderer_vulkan/vk_blit_helper.h" @@ -235,6 +236,55 @@ private: bool is_storage{}; }; +class Framebuffer : public VideoCore::FramebufferBase { +public: + explicit Framebuffer(Surface* const color, Surface* const depth_stencil, + vk::Rect2D render_area); + explicit Framebuffer(TextureRuntime& runtime, Surface* const color, + Surface* const depth_stencil, const Pica::Regs& regs, + Common::Rectangle surfaces_rect); + ~Framebuffer(); + + VideoCore::PixelFormat Format(VideoCore::SurfaceType type) const noexcept { + return formats[Index(type)]; + } + + [[nodiscard]] vk::Image Image(VideoCore::SurfaceType type) const noexcept { + return images[Index(type)]; + } + + [[nodiscard]] vk::ImageView ImageView(VideoCore::SurfaceType type) const noexcept { + return image_views[Index(type)]; + } + + bool HasAttachment(VideoCore::SurfaceType type) const noexcept { + return static_cast(image_views[Index(type)]); + } + + u32 Width() const noexcept { + return width; + } + + u32 Height() const noexcept { + return height; + } + + vk::Rect2D RenderArea() const noexcept { + return render_area; + } + +private: + void PrepareImages(Surface* const color, Surface* const depth_stencil); + +private: + std::array images{}; + std::array image_views{}; + std::array formats{}; + vk::Rect2D render_area{}; + u32 width{}; + u32 height{}; +}; + /** * @brief A sampler is used to configure the sampling parameters of a texture unit */ @@ -269,6 +319,7 @@ struct Traits { using RuntimeType = TextureRuntime; using SurfaceType = Surface; using Sampler = Sampler; + using Framebuffer = Framebuffer; }; using RasterizerCache = VideoCore::RasterizerCache;