rasterizer_cache: Wrap common draw operation in FramebufferBase
* Makes the rasterizer draw method much cleaner
This commit is contained in:
@ -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
|
||||
|
64
src/video_core/rasterizer_cache/framebuffer_base.cpp
Normal file
64
src/video_core/rasterizer_cache/framebuffer_base.cpp
Normal file
@ -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<u32> 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<s32>(static_cast<s32>(surfaces_rect.left) + viewport_rect.left * res_scale,
|
||||
surfaces_rect.left, surfaces_rect.right);
|
||||
draw_rect.top =
|
||||
std::clamp<s32>(static_cast<s32>(surfaces_rect.bottom) + viewport_rect.top * res_scale,
|
||||
surfaces_rect.bottom, surfaces_rect.top);
|
||||
draw_rect.right =
|
||||
std::clamp<s32>(static_cast<s32>(surfaces_rect.left) + viewport_rect.right * res_scale,
|
||||
surfaces_rect.left, surfaces_rect.right);
|
||||
draw_rect.bottom =
|
||||
std::clamp<s32>(static_cast<s32>(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<s32>(surfaces_rect.left + regs.rasterizer.scissor_test.x1 * res_scale);
|
||||
scissor_rect.bottom =
|
||||
static_cast<s32>(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<s32>(surfaces_rect.left + (regs.rasterizer.scissor_test.x2 + 1) * res_scale);
|
||||
scissor_rect.top =
|
||||
static_cast<s32>(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
|
86
src/video_core/rasterizer_cache/framebuffer_base.h
Normal file
86
src/video_core/rasterizer_cache/framebuffer_base.h
Normal file
@ -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<u32> 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<u32> DrawRect() const noexcept {
|
||||
return draw_rect;
|
||||
}
|
||||
|
||||
Common::Rectangle<s32> 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<SurfaceInterval, 2> intervals{};
|
||||
Common::Rectangle<s32> scissor_rect{};
|
||||
Common::Rectangle<u32> draw_rect{};
|
||||
ViewportInfo viewport;
|
||||
u32 res_scale{1};
|
||||
};
|
||||
|
||||
} // namespace VideoCore
|
@ -707,32 +707,28 @@ auto RasterizerCache<T>::GetTextureCube(const TextureCubeConfig& config) -> cons
|
||||
}
|
||||
|
||||
template <class T>
|
||||
auto RasterizerCache<T>::GetFramebufferSurfaces(bool using_color_fb, bool using_depth_fb,
|
||||
const Common::Rectangle<s32>& viewport_rect)
|
||||
-> SurfaceSurfaceRect_Tuple {
|
||||
auto RasterizerCache<T>::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<u32> viewport_clamped{
|
||||
static_cast<u32>(std::clamp(viewport_rect.left, 0, static_cast<s32>(config.GetWidth()))),
|
||||
static_cast<u32>(std::clamp(viewport_rect.top, 0, static_cast<s32>(config.GetHeight()))),
|
||||
static_cast<u32>(std::clamp(viewport_rect.right, 0, static_cast<s32>(config.GetWidth()))),
|
||||
static_cast<u32>(
|
||||
std::clamp(viewport_rect.bottom, 0, static_cast<s32>(config.GetHeight())))};
|
||||
const s32 framebuffer_width = config.GetWidth();
|
||||
const s32 framebuffer_height = config.GetHeight();
|
||||
const auto viewport_rect = regs.rasterizer.GetViewportRect();
|
||||
const Common::Rectangle<u32> viewport_clamped = {
|
||||
static_cast<u32>(std::clamp(viewport_rect.left, 0, framebuffer_width)),
|
||||
static_cast<u32>(std::clamp(viewport_rect.top, 0, framebuffer_height)),
|
||||
static_cast<u32>(std::clamp(viewport_rect.right, 0, framebuffer_width)),
|
||||
static_cast<u32>(std::clamp(viewport_rect.bottom, 0, framebuffer_height)),
|
||||
};
|
||||
|
||||
// get color and depth surfaces
|
||||
SurfaceParams color_params;
|
||||
@ -756,25 +752,25 @@ auto RasterizerCache<T>::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<u32> 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<u32> 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<u32> 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<T>::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 <class T>
|
||||
void RasterizerCache<T>::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 <class T>
|
||||
|
@ -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<typename T::SurfaceType>;
|
||||
using Sampler = typename T::Sampler;
|
||||
using Framebuffer = typename T::Framebuffer;
|
||||
|
||||
/// Declare rasterizer interval types
|
||||
using SurfaceMap = boost::icl::interval_map<PAddr, Surface, boost::icl::partial_absorber,
|
||||
@ -54,7 +55,6 @@ class RasterizerCache : NonCopyable {
|
||||
boost::icl::inter_section, SurfaceInterval>;
|
||||
|
||||
using SurfaceRect_Tuple = std::tuple<Surface, Common::Rectangle<u32>>;
|
||||
using SurfaceSurfaceRect_Tuple = std::tuple<Surface, Surface, Common::Rectangle<u32>>;
|
||||
using PageMap = boost::icl::interval_map<u32, int>;
|
||||
|
||||
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<s32>& 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<SamplerParams, SamplerId> samplers;
|
||||
|
||||
SlotVector<Sampler> slot_samplers;
|
||||
|
||||
struct RenderTargets {
|
||||
Surface color_surface;
|
||||
Surface depth_surface;
|
||||
};
|
||||
|
||||
RenderTargets render_targets;
|
||||
};
|
||||
|
||||
} // namespace VideoCore
|
||||
|
@ -5,6 +5,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <boost/icl/interval_set.hpp>
|
||||
#include "video_core/rasterizer_cache/surface_params.h"
|
||||
|
||||
namespace VideoCore {
|
||||
|
@ -4,13 +4,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <boost/icl/interval_set.hpp>
|
||||
#include "video_core/rasterizer_cache/utils.h"
|
||||
|
||||
namespace VideoCore {
|
||||
|
||||
using SurfaceInterval = boost::icl::right_open_interval<PAddr>;
|
||||
|
||||
class SurfaceParams {
|
||||
public:
|
||||
/// Returns true if other_surface matches exactly params
|
||||
|
@ -5,6 +5,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <span>
|
||||
#include <boost/icl/right_open_interval.hpp>
|
||||
#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<PAddr>;
|
||||
|
||||
struct Offset {
|
||||
constexpr auto operator<=>(const Offset&) const noexcept = default;
|
||||
|
||||
|
@ -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<u32>(std::clamp<s32>(static_cast<s32>(surfaces_rect.left) +
|
||||
viewport_rect_unscaled.left * res_scale,
|
||||
surfaces_rect.left, surfaces_rect.right)), // Left
|
||||
static_cast<u32>(std::clamp<s32>(static_cast<s32>(surfaces_rect.bottom) +
|
||||
viewport_rect_unscaled.top * res_scale,
|
||||
surfaces_rect.bottom, surfaces_rect.top)), // Top
|
||||
static_cast<u32>(std::clamp<s32>(static_cast<s32>(surfaces_rect.left) +
|
||||
viewport_rect_unscaled.right * res_scale,
|
||||
surfaces_rect.left, surfaces_rect.right)), // Right
|
||||
static_cast<u32>(std::clamp<s32>(static_cast<s32>(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<GLint>(surfaces_rect.left) + viewport_rect_unscaled.left * res_scale;
|
||||
state.viewport.y =
|
||||
static_cast<GLint>(surfaces_rect.bottom) + viewport_rect_unscaled.bottom * res_scale;
|
||||
state.viewport.width = static_cast<GLsizei>(viewport_rect_unscaled.GetWidth() * res_scale);
|
||||
state.viewport.height = static_cast<GLsizei>(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<GLint>(surfaces_rect.left + regs.rasterizer.scissor_test.x1 * res_scale);
|
||||
GLint scissor_y1 =
|
||||
static_cast<GLint>(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<GLint>(surfaces_rect.left + (regs.rasterizer.scissor_test.x2 + 1) * res_scale);
|
||||
GLint scissor_y2 = static_cast<GLint>(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;
|
||||
}
|
||||
|
@ -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<u32> 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);
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
#include <set>
|
||||
#include <span>
|
||||
#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<ReinterpreterList, VideoCore::PIXEL_FORMAT_COUNT> reinterpreters;
|
||||
std::unordered_multimap<VideoCore::HostTextureTag, OGLTexture> texture_recycler;
|
||||
std::unordered_map<u64, OGLFramebuffer, Common::IdentityHash<u64>> framebuffer_cache;
|
||||
StreamBuffer upload_buffer;
|
||||
std::vector<u8> 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<u32> 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<bool>(attachments[Index(type)]);
|
||||
}
|
||||
|
||||
private:
|
||||
std::array<GLuint, 2> 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<Traits>;
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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<u32>(std::clamp<s32>(static_cast<s32>(surfaces_rect.left) +
|
||||
viewport_rect_unscaled.left * res_scale,
|
||||
surfaces_rect.left, surfaces_rect.right)), // Left
|
||||
static_cast<u32>(std::clamp<s32>(static_cast<s32>(surfaces_rect.bottom) +
|
||||
viewport_rect_unscaled.top * res_scale,
|
||||
surfaces_rect.bottom, surfaces_rect.top)), // Top
|
||||
static_cast<u32>(std::clamp<s32>(static_cast<s32>(surfaces_rect.left) +
|
||||
viewport_rect_unscaled.right * res_scale,
|
||||
surfaces_rect.left, surfaces_rect.right)), // Right
|
||||
static_cast<u32>(std::clamp<s32>(static_cast<s32>(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<int>(surfaces_rect.left + regs.rasterizer.scissor_test.x1 * res_scale);
|
||||
int scissor_y1 =
|
||||
static_cast<int>(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<int>(surfaces_rect.left + (regs.rasterizer.scissor_test.x2 + 1) * res_scale);
|
||||
int scissor_y2 =
|
||||
static_cast<int>(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<s32>(draw_rect.left), static_cast<s32>(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<Surface&>(*framebuffer.Color()), temp, copy);
|
||||
pipeline_cache.BindTexture(texture_index, temp.ImageView());
|
||||
} else {
|
||||
pipeline_cache.BindTexture(texture_index, surface->ImageView());
|
||||
|
@ -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);
|
||||
|
@ -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<VideoCore::PixelFormat, 2> formats{};
|
||||
std::array<vk::Image, 2> images{};
|
||||
std::array<vk::ImageView, 2> 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();
|
||||
}
|
||||
|
@ -39,6 +39,8 @@ struct hash<Vulkan::FramebufferInfo> {
|
||||
|
||||
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,
|
||||
|
@ -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<u32> 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<u32>::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;
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include <set>
|
||||
#include <span>
|
||||
#include <vulkan/vulkan_hash.hpp>
|
||||
#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<u32> 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<bool>(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<vk::Image, 2> images{};
|
||||
std::array<vk::ImageView, 2> image_views{};
|
||||
std::array<VideoCore::PixelFormat, 2> 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<Traits>;
|
||||
|
Reference in New Issue
Block a user