rasterizer_cache: Remove runtime allocation caching (#6705)
* rasterizer_cache: Sentence surfaces * gl_texture_runtime: Remove runtime side allocation cache * rasterizer_cache: Adjust surface scale during reinterpreration * Fixes pixelated outlines. Also allows to remove the d24s8 specific hack and is more generic in general * rasterizer_cache: Remove Expand flag * Begone! * rasterizer_cache: Cache framebuffers with surface id * rasterizer_cache: Sentence texture cubes * renderer_opengl: Move texture mailbox to separate file * Makes renderer_opengl cleaner overall and allows to report removal threshold from runtime instead of hardcoding. Vulkan requires this * rasterizer_cache: Dont flush cache on layout change * rasterizer_cache: Overhaul framebuffer management * video_core: Remove duplicate * rasterizer_cache: Sentence custom surfaces * Vulkan cannot destroy images immediately so this ensures we use our garbage collector for that purpose
This commit is contained in:
parent
3fedc68230
commit
a955f02771
|
@ -62,12 +62,29 @@ public:
|
||||||
return SlotId{index};
|
return SlotId{index};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename... Args>
|
||||||
|
[[nodiscard]] SlotId swap_and_insert(SlotId existing_id, Args&&... args) noexcept {
|
||||||
|
const u32 index = FreeValueIndex();
|
||||||
|
T& existing_value = values[existing_id.index].object;
|
||||||
|
|
||||||
|
new (&values[index].object) T(std::move(existing_value));
|
||||||
|
existing_value.~T();
|
||||||
|
new (&values[existing_id.index].object) T(std::forward<Args>(args)...);
|
||||||
|
SetStorageBit(index);
|
||||||
|
|
||||||
|
return SlotId{index};
|
||||||
|
}
|
||||||
|
|
||||||
void erase(SlotId id) noexcept {
|
void erase(SlotId id) noexcept {
|
||||||
values[id.index].object.~T();
|
values[id.index].object.~T();
|
||||||
free_list.push_back(id.index);
|
free_list.push_back(id.index);
|
||||||
ResetStorageBit(id.index);
|
ResetStorageBit(id.index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t size() const noexcept {
|
||||||
|
return values_capacity - free_list.size();
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct NonTrivialDummy {
|
struct NonTrivialDummy {
|
||||||
NonTrivialDummy() noexcept {}
|
NonTrivialDummy() noexcept {}
|
||||||
|
@ -93,7 +110,7 @@ private:
|
||||||
return ((stored_bitset[index / 64] >> (index % 64)) & 1) != 0;
|
return ((stored_bitset[index / 64] >> (index % 64)) & 1) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ValidateIndex(SlotId id) const noexcept {
|
void ValidateIndex([[maybe_unused]] SlotId id) const noexcept {
|
||||||
DEBUG_ASSERT(id);
|
DEBUG_ASSERT(id);
|
||||||
DEBUG_ASSERT(id.index / 64 < stored_bitset.size());
|
DEBUG_ASSERT(id.index / 64 < stored_bitset.size());
|
||||||
DEBUG_ASSERT(((stored_bitset[id.index / 64] >> (id.index % 64)) & 1) != 0);
|
DEBUG_ASSERT(((stored_bitset[id.index / 64] >> (id.index % 64)) & 1) != 0);
|
||||||
|
|
|
@ -617,9 +617,7 @@ void System::ApplySettings() {
|
||||||
if (VideoCore::g_renderer) {
|
if (VideoCore::g_renderer) {
|
||||||
auto& settings = VideoCore::g_renderer->Settings();
|
auto& settings = VideoCore::g_renderer->Settings();
|
||||||
settings.bg_color_update_requested = true;
|
settings.bg_color_update_requested = true;
|
||||||
settings.sampler_update_requested = true;
|
|
||||||
settings.shader_update_requested = true;
|
settings.shader_update_requested = true;
|
||||||
settings.texture_filter_update_requested = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsPoweredOn()) {
|
if (IsPoweredOn()) {
|
||||||
|
|
|
@ -34,7 +34,6 @@ add_library(video_core STATIC
|
||||||
regs_texturing.h
|
regs_texturing.h
|
||||||
renderer_base.cpp
|
renderer_base.cpp
|
||||||
renderer_base.h
|
renderer_base.h
|
||||||
rasterizer_cache/framebuffer_base.cpp
|
|
||||||
rasterizer_cache/framebuffer_base.h
|
rasterizer_cache/framebuffer_base.h
|
||||||
rasterizer_cache/pixel_format.cpp
|
rasterizer_cache/pixel_format.cpp
|
||||||
rasterizer_cache/pixel_format.h
|
rasterizer_cache/pixel_format.h
|
||||||
|
@ -76,6 +75,8 @@ add_library(video_core STATIC
|
||||||
renderer_opengl/gl_state.h
|
renderer_opengl/gl_state.h
|
||||||
renderer_opengl/gl_stream_buffer.cpp
|
renderer_opengl/gl_stream_buffer.cpp
|
||||||
renderer_opengl/gl_stream_buffer.h
|
renderer_opengl/gl_stream_buffer.h
|
||||||
|
renderer_opengl/gl_texture_mailbox.cpp
|
||||||
|
renderer_opengl/gl_texture_mailbox.h
|
||||||
renderer_opengl/gl_texture_runtime.cpp
|
renderer_opengl/gl_texture_runtime.cpp
|
||||||
renderer_opengl/gl_texture_runtime.h
|
renderer_opengl/gl_texture_runtime.h
|
||||||
renderer_opengl/gl_vars.cpp
|
renderer_opengl/gl_vars.cpp
|
||||||
|
|
|
@ -1,73 +0,0 @@
|
||||||
// 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, const SurfaceBase* color, u32 color_level,
|
|
||||||
const SurfaceBase* depth_stencil, u32 depth_level,
|
|
||||||
Common::Rectangle<u32> surfaces_rect) {
|
|
||||||
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 = static_cast<s32>(surfaces_rect.left) + viewport_rect.left * res_scale;
|
|
||||||
viewport.y = static_cast<s32>(surfaces_rect.bottom) + viewport_rect.bottom * res_scale;
|
|
||||||
viewport.width = static_cast<s32>(viewport_rect.GetWidth() * res_scale);
|
|
||||||
viewport.height = static_cast<s32>(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);
|
|
||||||
|
|
||||||
// Rendering to mipmaps is something quite rare so log it when it occurs.
|
|
||||||
if (color_level != 0) {
|
|
||||||
LOG_WARNING(HW_GPU, "Game is rendering to color mipmap {}", color_level);
|
|
||||||
}
|
|
||||||
if (depth_level != 0) {
|
|
||||||
LOG_WARNING(HW_GPU, "Game is rendering to depth mipmap {}", depth_level);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Query surface invalidation intervals
|
|
||||||
const Common::Rectangle draw_rect_unscaled{draw_rect / res_scale};
|
|
||||||
if (color) {
|
|
||||||
color_params = *color;
|
|
||||||
intervals[0] = color->GetSubRectInterval(draw_rect_unscaled, color_level);
|
|
||||||
}
|
|
||||||
if (depth_stencil) {
|
|
||||||
depth_params = *depth_stencil;
|
|
||||||
intervals[1] = depth_stencil->GetSubRectInterval(draw_rect_unscaled, depth_level);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace VideoCore
|
|
|
@ -4,12 +4,11 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/hash.h"
|
||||||
#include "common/math_util.h"
|
#include "common/math_util.h"
|
||||||
|
#include "video_core/rasterizer_cache/slot_id.h"
|
||||||
#include "video_core/rasterizer_cache/surface_params.h"
|
#include "video_core/rasterizer_cache/surface_params.h"
|
||||||
|
#include "video_core/regs_rasterizer.h"
|
||||||
namespace Pica {
|
|
||||||
struct Regs;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace VideoCore {
|
namespace VideoCore {
|
||||||
|
|
||||||
|
@ -22,31 +21,109 @@ struct ViewportInfo {
|
||||||
s32 height;
|
s32 height;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct FramebufferParams {
|
||||||
|
SurfaceId color_id;
|
||||||
|
SurfaceId depth_id;
|
||||||
|
u32 color_level;
|
||||||
|
u32 depth_level;
|
||||||
|
bool shadow_rendering;
|
||||||
|
INSERT_PADDING_BYTES(3);
|
||||||
|
|
||||||
|
bool operator==(const FramebufferParams& params) const noexcept {
|
||||||
|
return std::memcmp(this, ¶ms, sizeof(FramebufferParams)) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 Hash() const noexcept {
|
||||||
|
return Common::ComputeHash64(this, sizeof(FramebufferParams));
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 Index(VideoCore::SurfaceType type) const noexcept {
|
||||||
|
switch (type) {
|
||||||
|
case VideoCore::SurfaceType::Color:
|
||||||
|
return 0;
|
||||||
|
case VideoCore::SurfaceType::Depth:
|
||||||
|
case VideoCore::SurfaceType::DepthStencil:
|
||||||
|
return 1;
|
||||||
|
default:
|
||||||
|
LOG_CRITICAL(HW_GPU, "Unknown surface type in framebuffer");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
static_assert(std::has_unique_object_representations_v<FramebufferParams>,
|
||||||
|
"FramebufferParams is not suitable for hashing");
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
class RasterizerCache;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A framebuffer is a lightweight abstraction over a pair of surfaces and provides
|
* @brief FramebufferHelper is a RAII wrapper over backend specific framebuffer handle that
|
||||||
* metadata about them.
|
* provides the viewport/scissor/draw rectanges and performs automatic rasterizer cache invalidation
|
||||||
|
* when out of scope.
|
||||||
*/
|
*/
|
||||||
class FramebufferBase {
|
template <class T>
|
||||||
|
class FramebufferHelper {
|
||||||
public:
|
public:
|
||||||
FramebufferBase();
|
explicit FramebufferHelper(RasterizerCache<T>* res_cache_, typename T::Framebuffer* fb_,
|
||||||
FramebufferBase(const Pica::Regs& regs, const SurfaceBase* color, u32 color_level,
|
const Pica::RasterizerRegs& regs,
|
||||||
const SurfaceBase* depth_stencil, u32 depth_level,
|
Common::Rectangle<u32> surfaces_rect)
|
||||||
Common::Rectangle<u32> surfaces_rect);
|
: res_cache{res_cache_}, fb{fb_} {
|
||||||
|
const u32 res_scale = fb->Scale();
|
||||||
|
|
||||||
SurfaceParams ColorParams() const noexcept {
|
// Determine the draw rectangle (render area + scissor)
|
||||||
return color_params;
|
const Common::Rectangle viewport_rect = regs.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 = static_cast<s32>(surfaces_rect.left) + viewport_rect.left * res_scale;
|
||||||
|
viewport.y = static_cast<s32>(surfaces_rect.bottom) + viewport_rect.bottom * res_scale;
|
||||||
|
viewport.width = static_cast<s32>(viewport_rect.GetWidth() * res_scale);
|
||||||
|
viewport.height = static_cast<s32>(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.scissor_test.x1 * res_scale);
|
||||||
|
scissor_rect.bottom =
|
||||||
|
static_cast<s32>(surfaces_rect.bottom + regs.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.scissor_test.x2 + 1) * res_scale);
|
||||||
|
scissor_rect.top =
|
||||||
|
static_cast<s32>(surfaces_rect.bottom + (regs.scissor_test.y2 + 1) * res_scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
SurfaceParams DepthParams() const noexcept {
|
~FramebufferHelper() {
|
||||||
return depth_params;
|
const Common::Rectangle draw_rect_unscaled{draw_rect / fb->Scale()};
|
||||||
|
const auto invalidate = [&](SurfaceId surface_id, u32 level) {
|
||||||
|
const auto& surface = res_cache->GetSurface(surface_id);
|
||||||
|
const SurfaceInterval interval = surface.GetSubRectInterval(draw_rect_unscaled, level);
|
||||||
|
const PAddr addr = boost::icl::first(interval);
|
||||||
|
const u32 size = boost::icl::length(interval);
|
||||||
|
res_cache->InvalidateRegion(addr, size, surface_id);
|
||||||
|
};
|
||||||
|
if (fb->color_id) {
|
||||||
|
invalidate(fb->color_id, fb->color_level);
|
||||||
|
}
|
||||||
|
if (fb->depth_id) {
|
||||||
|
invalidate(fb->depth_id, fb->depth_level);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SurfaceInterval Interval(SurfaceType type) const noexcept {
|
typename T::Framebuffer* Framebuffer() const noexcept {
|
||||||
return intervals[Index(type)];
|
return fb;
|
||||||
}
|
|
||||||
|
|
||||||
u32 ResolutionScale() const noexcept {
|
|
||||||
return res_scale;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Common::Rectangle<u32> DrawRect() const noexcept {
|
Common::Rectangle<u32> DrawRect() const noexcept {
|
||||||
|
@ -61,28 +138,21 @@ public:
|
||||||
return viewport;
|
return viewport;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
private:
|
||||||
u32 Index(VideoCore::SurfaceType type) const noexcept {
|
RasterizerCache<T>* res_cache;
|
||||||
switch (type) {
|
typename T::Framebuffer* fb;
|
||||||
case VideoCore::SurfaceType::Color:
|
Common::Rectangle<s32> scissor_rect;
|
||||||
return 0;
|
Common::Rectangle<u32> draw_rect;
|
||||||
case VideoCore::SurfaceType::Depth:
|
|
||||||
case VideoCore::SurfaceType::DepthStencil:
|
|
||||||
return 1;
|
|
||||||
default:
|
|
||||||
LOG_CRITICAL(HW_GPU, "Unknown surface type in framebuffer");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
SurfaceParams color_params{};
|
|
||||||
SurfaceParams depth_params{};
|
|
||||||
std::array<SurfaceInterval, 2> intervals{};
|
|
||||||
Common::Rectangle<s32> scissor_rect{};
|
|
||||||
Common::Rectangle<u32> draw_rect{};
|
|
||||||
ViewportInfo viewport;
|
ViewportInfo viewport;
|
||||||
u32 res_scale{1};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace VideoCore
|
} // namespace VideoCore
|
||||||
|
|
||||||
|
namespace std {
|
||||||
|
template <>
|
||||||
|
struct hash<VideoCore::FramebufferParams> {
|
||||||
|
std::size_t operator()(const VideoCore::FramebufferParams& params) const noexcept {
|
||||||
|
return params.Hash();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace std
|
||||||
|
|
|
@ -37,7 +37,7 @@ RasterizerCache<T>::RasterizerCache(Memory::MemorySystem& memory_,
|
||||||
Pica::Regs& regs_, RendererBase& renderer_)
|
Pica::Regs& regs_, RendererBase& renderer_)
|
||||||
: memory{memory_}, custom_tex_manager{custom_tex_manager_}, runtime{runtime_}, regs{regs_},
|
: memory{memory_}, custom_tex_manager{custom_tex_manager_}, runtime{runtime_}, regs{regs_},
|
||||||
renderer{renderer_}, resolution_scale_factor{renderer.GetResolutionScaleFactor()},
|
renderer{renderer_}, resolution_scale_factor{renderer.GetResolutionScaleFactor()},
|
||||||
use_filter{Settings::values.texture_filter.GetValue() != Settings::TextureFilter::None},
|
filter{Settings::values.texture_filter.GetValue()},
|
||||||
dump_textures{Settings::values.dump_textures.GetValue()},
|
dump_textures{Settings::values.dump_textures.GetValue()},
|
||||||
use_custom_textures{Settings::values.custom_textures.GetValue()} {
|
use_custom_textures{Settings::values.custom_textures.GetValue()} {
|
||||||
using TextureConfig = Pica::TexturingRegs::TextureConfig;
|
using TextureConfig = Pica::TexturingRegs::TextureConfig;
|
||||||
|
@ -76,17 +76,21 @@ RasterizerCache<T>::~RasterizerCache() {
|
||||||
template <class T>
|
template <class T>
|
||||||
void RasterizerCache<T>::TickFrame() {
|
void RasterizerCache<T>::TickFrame() {
|
||||||
custom_tex_manager.TickFrame();
|
custom_tex_manager.TickFrame();
|
||||||
|
RunGarbageCollector();
|
||||||
|
|
||||||
|
const auto new_filter = Settings::values.texture_filter.GetValue();
|
||||||
|
if (filter != new_filter) [[unlikely]] {
|
||||||
|
filter = new_filter;
|
||||||
|
UnregisterAll();
|
||||||
|
}
|
||||||
|
|
||||||
const u32 scale_factor = renderer.GetResolutionScaleFactor();
|
const u32 scale_factor = renderer.GetResolutionScaleFactor();
|
||||||
const bool resolution_scale_changed = resolution_scale_factor != scale_factor;
|
const bool resolution_scale_changed = resolution_scale_factor != scale_factor;
|
||||||
const bool use_custom_texture_changed =
|
const bool use_custom_texture_changed =
|
||||||
Settings::values.custom_textures.GetValue() != use_custom_textures;
|
Settings::values.custom_textures.GetValue() != use_custom_textures;
|
||||||
const bool texture_filter_changed =
|
|
||||||
renderer.Settings().texture_filter_update_requested.exchange(false);
|
|
||||||
|
|
||||||
if (resolution_scale_changed || texture_filter_changed || use_custom_texture_changed) {
|
if (resolution_scale_changed || use_custom_texture_changed) {
|
||||||
resolution_scale_factor = scale_factor;
|
resolution_scale_factor = scale_factor;
|
||||||
use_filter = Settings::values.texture_filter.GetValue() != Settings::TextureFilter::None;
|
|
||||||
use_custom_textures = Settings::values.custom_textures.GetValue();
|
use_custom_textures = Settings::values.custom_textures.GetValue();
|
||||||
if (use_custom_textures) {
|
if (use_custom_textures) {
|
||||||
custom_tex_manager.FindCustomTextures();
|
custom_tex_manager.FindCustomTextures();
|
||||||
|
@ -95,6 +99,34 @@ void RasterizerCache<T>::TickFrame() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
void RasterizerCache<T>::RunGarbageCollector() {
|
||||||
|
frame_tick++;
|
||||||
|
for (auto it = sentenced.begin(); it != sentenced.end();) {
|
||||||
|
const auto [surface_id, tick] = *it;
|
||||||
|
if (frame_tick - tick <= runtime.RemoveThreshold()) {
|
||||||
|
it++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
RemoveFramebuffers(surface_id);
|
||||||
|
slot_surfaces.erase(surface_id);
|
||||||
|
it = sentenced.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
void RasterizerCache<T>::RemoveFramebuffers(SurfaceId surface_id) {
|
||||||
|
for (auto it = framebuffers.begin(); it != framebuffers.end();) {
|
||||||
|
const auto& params = it->first;
|
||||||
|
if (params.color_id == surface_id || params.depth_id == surface_id) {
|
||||||
|
slot_framebuffers.erase(it->second);
|
||||||
|
it = framebuffers.erase(it);
|
||||||
|
} else {
|
||||||
|
it++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
bool RasterizerCache<T>::AccelerateTextureCopy(const GPU::Regs::DisplayTransferConfig& config) {
|
bool RasterizerCache<T>::AccelerateTextureCopy(const GPU::Regs::DisplayTransferConfig& config) {
|
||||||
const DebugScope scope{runtime, Common::Vec4f{0.f, 0.f, 1.f, 1.f},
|
const DebugScope scope{runtime, Common::Vec4f{0.f, 0.f, 1.f, 1.f},
|
||||||
|
@ -322,29 +354,46 @@ template <class T>
|
||||||
void RasterizerCache<T>::CopySurface(Surface& src_surface, Surface& dst_surface,
|
void RasterizerCache<T>::CopySurface(Surface& src_surface, Surface& dst_surface,
|
||||||
SurfaceInterval copy_interval) {
|
SurfaceInterval copy_interval) {
|
||||||
MICROPROFILE_SCOPE(RasterizerCache_CopySurface);
|
MICROPROFILE_SCOPE(RasterizerCache_CopySurface);
|
||||||
|
|
||||||
const PAddr copy_addr = copy_interval.lower();
|
const PAddr copy_addr = copy_interval.lower();
|
||||||
const SurfaceParams subrect_params = dst_surface.FromInterval(copy_interval);
|
const SurfaceParams subrect_params = dst_surface.FromInterval(copy_interval);
|
||||||
const auto dst_rect = dst_surface.GetScaledSubRect(subrect_params);
|
|
||||||
ASSERT(subrect_params.GetInterval() == copy_interval);
|
ASSERT(subrect_params.GetInterval() == copy_interval);
|
||||||
|
|
||||||
if (src_surface.type == SurfaceType::Fill) {
|
if (src_surface.type == SurfaceType::Fill) {
|
||||||
const TextureClear clear = {
|
const TextureClear clear = {
|
||||||
.texture_level = dst_surface.LevelOf(copy_addr),
|
.texture_level = dst_surface.LevelOf(copy_addr),
|
||||||
.texture_rect = dst_rect,
|
.texture_rect = dst_surface.GetScaledSubRect(subrect_params),
|
||||||
.value = src_surface.MakeClearValue(copy_addr, dst_surface.pixel_format),
|
.value = src_surface.MakeClearValue(copy_addr, dst_surface.pixel_format),
|
||||||
};
|
};
|
||||||
runtime.ClearTexture(dst_surface, clear);
|
runtime.ClearTexture(dst_surface, clear);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TextureBlit blit = {
|
const u32 src_scale = src_surface.res_scale;
|
||||||
.src_level = src_surface.LevelOf(copy_addr),
|
const u32 dst_scale = dst_surface.res_scale;
|
||||||
.dst_level = dst_surface.LevelOf(copy_addr),
|
if (src_scale > dst_scale) {
|
||||||
.src_rect = src_surface.GetScaledSubRect(subrect_params),
|
dst_surface.ScaleUp(src_scale);
|
||||||
.dst_rect = dst_rect,
|
}
|
||||||
};
|
|
||||||
runtime.BlitTextures(src_surface, dst_surface, blit);
|
const auto src_rect = src_surface.GetScaledSubRect(subrect_params);
|
||||||
|
const auto dst_rect = dst_surface.GetScaledSubRect(subrect_params);
|
||||||
|
if (src_scale == dst_scale) {
|
||||||
|
const TextureCopy copy = {
|
||||||
|
.src_level = src_surface.LevelOf(copy_addr),
|
||||||
|
.dst_level = dst_surface.LevelOf(copy_addr),
|
||||||
|
.src_offset = {src_rect.left, src_rect.bottom},
|
||||||
|
.dst_offset = {dst_rect.left, dst_rect.bottom},
|
||||||
|
.extent = {src_rect.GetWidth(), src_rect.GetHeight()},
|
||||||
|
};
|
||||||
|
runtime.CopyTextures(src_surface, dst_surface, copy);
|
||||||
|
} else {
|
||||||
|
const TextureBlit blit = {
|
||||||
|
.src_level = src_surface.LevelOf(copy_addr),
|
||||||
|
.dst_level = dst_surface.LevelOf(copy_addr),
|
||||||
|
.src_rect = src_rect,
|
||||||
|
.dst_rect = dst_rect,
|
||||||
|
};
|
||||||
|
runtime.BlitTextures(src_surface, dst_surface, blit);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
|
@ -361,33 +410,7 @@ SurfaceId RasterizerCache<T>::GetSurface(const SurfaceParams& params, ScaleMatch
|
||||||
SurfaceId surface_id = FindMatch<MatchFlags::Exact>(params, match_res_scale);
|
SurfaceId surface_id = FindMatch<MatchFlags::Exact>(params, match_res_scale);
|
||||||
|
|
||||||
if (!surface_id) {
|
if (!surface_id) {
|
||||||
u16 target_res_scale = params.res_scale;
|
surface_id = CreateSurface(params);
|
||||||
if (match_res_scale != ScaleMatch::Exact) {
|
|
||||||
// This surface may have a subrect of another surface with a higher res_scale, find
|
|
||||||
// it to adjust our params
|
|
||||||
SurfaceParams find_params = params;
|
|
||||||
SurfaceId expandable_id = FindMatch<MatchFlags::Expand>(find_params, match_res_scale);
|
|
||||||
if (expandable_id) {
|
|
||||||
Surface& expandable = slot_surfaces[expandable_id];
|
|
||||||
if (expandable.res_scale > target_res_scale) {
|
|
||||||
target_res_scale = expandable.res_scale;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Keep res_scale when reinterpreting d24s8 -> rgba8
|
|
||||||
if (params.pixel_format == PixelFormat::RGBA8) {
|
|
||||||
find_params.pixel_format = PixelFormat::D24S8;
|
|
||||||
expandable_id = FindMatch<MatchFlags::Expand>(find_params, match_res_scale);
|
|
||||||
if (expandable_id) {
|
|
||||||
Surface& expandable = slot_surfaces[expandable_id];
|
|
||||||
if (expandable.res_scale > target_res_scale) {
|
|
||||||
target_res_scale = expandable.res_scale;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SurfaceParams new_params = params;
|
|
||||||
new_params.res_scale = target_res_scale;
|
|
||||||
surface_id = CreateSurface(new_params);
|
|
||||||
RegisterSurface(surface_id);
|
RegisterSurface(surface_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -429,31 +452,6 @@ typename RasterizerCache<T>::SurfaceRect_Tuple RasterizerCache<T>::GetSurfaceSub
|
||||||
aligned_params.UpdateParams();
|
aligned_params.UpdateParams();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for a surface we can expand before creating a new one
|
|
||||||
if (!surface_id) {
|
|
||||||
surface_id = FindMatch<MatchFlags::Expand>(aligned_params, match_res_scale);
|
|
||||||
if (surface_id) {
|
|
||||||
Surface& surface = slot_surfaces[surface_id];
|
|
||||||
aligned_params.width = aligned_params.stride;
|
|
||||||
aligned_params.UpdateParams();
|
|
||||||
|
|
||||||
SurfaceParams new_params = surface;
|
|
||||||
new_params.addr = std::min(aligned_params.addr, surface.addr);
|
|
||||||
new_params.end = std::max(aligned_params.end, surface.end);
|
|
||||||
new_params.size = new_params.end - new_params.addr;
|
|
||||||
new_params.height =
|
|
||||||
new_params.size / aligned_params.BytesInPixels(aligned_params.stride);
|
|
||||||
new_params.UpdateParams();
|
|
||||||
ASSERT(new_params.size % aligned_params.BytesInPixels(aligned_params.stride) == 0);
|
|
||||||
|
|
||||||
SurfaceId new_surface_id = CreateSurface(new_params);
|
|
||||||
DuplicateSurface(surface_id, new_surface_id);
|
|
||||||
UnregisterSurface(surface_id);
|
|
||||||
RegisterSurface(new_surface_id);
|
|
||||||
surface_id = new_surface_id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// No subrect found - create and return a new surface
|
// No subrect found - create and return a new surface
|
||||||
if (!surface_id) {
|
if (!surface_id) {
|
||||||
SurfaceParams new_params = aligned_params;
|
SurfaceParams new_params = aligned_params;
|
||||||
|
@ -499,7 +497,7 @@ SurfaceId RasterizerCache<T>::GetTextureSurface(const Pica::Texture::TextureInfo
|
||||||
params.levels = max_level + 1;
|
params.levels = max_level + 1;
|
||||||
params.is_tiled = true;
|
params.is_tiled = true;
|
||||||
params.pixel_format = PixelFormatFromTextureFormat(info.format);
|
params.pixel_format = PixelFormatFromTextureFormat(info.format);
|
||||||
params.res_scale = use_filter ? resolution_scale_factor : 1;
|
params.res_scale = filter != Settings::TextureFilter::None ? resolution_scale_factor : 1;
|
||||||
params.UpdateParams();
|
params.UpdateParams();
|
||||||
|
|
||||||
const u32 min_width = info.width >> max_level;
|
const u32 min_width = info.width >> max_level;
|
||||||
|
@ -552,7 +550,7 @@ typename T::Surface& RasterizerCache<T>::GetTextureCube(const TextureCubeConfig&
|
||||||
.height = config.width,
|
.height = config.width,
|
||||||
.stride = config.width,
|
.stride = config.width,
|
||||||
.levels = config.levels,
|
.levels = config.levels,
|
||||||
.res_scale = use_filter ? resolution_scale_factor : 1,
|
.res_scale = filter != Settings::TextureFilter::None ? resolution_scale_factor : 1,
|
||||||
.texture_type = TextureType::CubeMap,
|
.texture_type = TextureType::CubeMap,
|
||||||
.pixel_format = PixelFormatFromTextureFormat(config.format),
|
.pixel_format = PixelFormatFromTextureFormat(config.format),
|
||||||
.type = SurfaceType::Texture,
|
.type = SurfaceType::Texture,
|
||||||
|
@ -609,8 +607,8 @@ typename T::Surface& RasterizerCache<T>::GetTextureCube(const TextureCubeConfig&
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
typename T::Framebuffer RasterizerCache<T>::GetFramebufferSurfaces(bool using_color_fb,
|
FramebufferHelper<T> RasterizerCache<T>::GetFramebufferSurfaces(bool using_color_fb,
|
||||||
bool using_depth_fb) {
|
bool using_depth_fb) {
|
||||||
const auto& config = regs.framebuffer.framebuffer;
|
const auto& config = regs.framebuffer.framebuffer;
|
||||||
|
|
||||||
const s32 framebuffer_width = config.GetWidth();
|
const s32 framebuffer_width = config.GetWidth();
|
||||||
|
@ -692,35 +690,20 @@ typename T::Framebuffer RasterizerCache<T>::GetFramebufferSurfaces(bool using_co
|
||||||
boost::icl::length(depth_vp_interval));
|
boost::icl::length(depth_vp_interval));
|
||||||
}
|
}
|
||||||
|
|
||||||
render_targets = RenderTargets{
|
fb_params = FramebufferParams{
|
||||||
.color_id = color_id,
|
.color_id = color_id,
|
||||||
.depth_id = depth_id,
|
.depth_id = depth_id,
|
||||||
|
.color_level = color_level,
|
||||||
|
.depth_level = depth_level,
|
||||||
|
.shadow_rendering = regs.framebuffer.IsShadowRendering(),
|
||||||
};
|
};
|
||||||
|
|
||||||
return Framebuffer{runtime, color_surface, color_level, depth_surface,
|
auto [it, new_framebuffer] = framebuffers.try_emplace(fb_params);
|
||||||
depth_level, regs, fb_rect};
|
if (new_framebuffer) {
|
||||||
}
|
it->second = slot_framebuffers.insert(runtime, fb_params, color_surface, depth_surface);
|
||||||
|
}
|
||||||
|
|
||||||
template <class T>
|
return FramebufferHelper<T>{this, &slot_framebuffers[it->second], regs.rasterizer, fb_rect};
|
||||||
void RasterizerCache<T>::InvalidateFramebuffer(const Framebuffer& framebuffer) {
|
|
||||||
const auto invalidate = [&](SurfaceId surface_id) {
|
|
||||||
if (!surface_id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Surface& surface = slot_surfaces[surface_id];
|
|
||||||
const SurfaceInterval interval = framebuffer.Interval(surface.type);
|
|
||||||
const PAddr addr = boost::icl::first(interval);
|
|
||||||
const u32 size = boost::icl::length(interval);
|
|
||||||
InvalidateRegion(addr, size, surface_id);
|
|
||||||
};
|
|
||||||
const bool has_color = framebuffer.HasAttachment(SurfaceType::Color);
|
|
||||||
const bool has_depth = framebuffer.HasAttachment(SurfaceType::DepthStencil);
|
|
||||||
if (has_color) {
|
|
||||||
invalidate(render_targets.color_id);
|
|
||||||
}
|
|
||||||
if (has_depth) {
|
|
||||||
invalidate(render_targets.depth_id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
|
@ -875,9 +858,6 @@ SurfaceId RasterizerCache<T>::FindMatch(const SurfaceParams& params, ScaleMatch
|
||||||
surface.CanReinterpret(params);
|
surface.CanReinterpret(params);
|
||||||
return std::make_pair(matched, copy_interval);
|
return std::make_pair(matched, copy_interval);
|
||||||
});
|
});
|
||||||
IsMatch_Helper(std::integral_constant<MatchFlags, MatchFlags::Expand>{}, [&] {
|
|
||||||
return std::make_pair(surface.CanExpand(params), surface.GetInterval());
|
|
||||||
});
|
|
||||||
IsMatch_Helper(std::integral_constant<MatchFlags, MatchFlags::TexCopy>{}, [&] {
|
IsMatch_Helper(std::integral_constant<MatchFlags, MatchFlags::TexCopy>{}, [&] {
|
||||||
return std::make_pair(surface.CanTexCopy(params), surface.GetInterval());
|
return std::make_pair(surface.CanTexCopy(params), surface.GetInterval());
|
||||||
});
|
});
|
||||||
|
@ -1068,14 +1048,12 @@ bool RasterizerCache<T>::UploadCustomSurface(SurfaceId surface_id, SurfaceInterv
|
||||||
|
|
||||||
const auto upload = [this, level, surface_id, material]() -> bool {
|
const auto upload = [this, level, surface_id, material]() -> bool {
|
||||||
Surface& surface = slot_surfaces[surface_id];
|
Surface& surface = slot_surfaces[surface_id];
|
||||||
if (False(surface.flags & SurfaceFlagBits::Custom)) {
|
ASSERT_MSG(True(surface.flags & SurfaceFlagBits::Custom),
|
||||||
LOG_ERROR(HW_GPU, "Surface is not suitable for custom upload, aborting!");
|
"Surface is not suitable for custom upload, aborting!");
|
||||||
return false;
|
if (!surface.IsCustom()) {
|
||||||
}
|
const SurfaceId old_id =
|
||||||
if (!surface.IsCustom() && !surface.Swap(material)) {
|
slot_surfaces.swap_and_insert(surface_id, runtime, surface, material);
|
||||||
LOG_ERROR(HW_GPU, "Custom compressed format {} unsupported by host GPU",
|
sentenced.emplace_back(old_id, frame_tick);
|
||||||
material->format);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
surface.UploadCustom(material, level);
|
surface.UploadCustom(material, level);
|
||||||
if (custom_tex_manager.SkipMipmaps()) {
|
if (custom_tex_manager.SkipMipmaps()) {
|
||||||
|
@ -1159,6 +1137,10 @@ bool RasterizerCache<T>::ValidateByReinterpretation(Surface& surface, SurfacePar
|
||||||
if (boost::icl::is_empty(copy_interval & interval)) {
|
if (boost::icl::is_empty(copy_interval & interval)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
const u32 res_scale = src_surface.res_scale;
|
||||||
|
if (res_scale > surface.res_scale) {
|
||||||
|
surface.ScaleUp(res_scale);
|
||||||
|
}
|
||||||
const PAddr addr = boost::icl::lower(interval);
|
const PAddr addr = boost::icl::lower(interval);
|
||||||
const SurfaceParams copy_params = surface.FromInterval(copy_interval);
|
const SurfaceParams copy_params = surface.FromInterval(copy_interval);
|
||||||
const TextureBlit reinterpret = {
|
const TextureBlit reinterpret = {
|
||||||
|
@ -1229,25 +1211,24 @@ void RasterizerCache<T>::FlushRegion(PAddr addr, u32 size, SurfaceId flush_surfa
|
||||||
SurfaceRegions flushed_intervals;
|
SurfaceRegions flushed_intervals;
|
||||||
|
|
||||||
for (const auto& [region, surface_id] : RangeFromInterval(dirty_regions, flush_interval)) {
|
for (const auto& [region, surface_id] : RangeFromInterval(dirty_regions, flush_interval)) {
|
||||||
// Small sizes imply that this most likely comes from the cpu, flush the entire region
|
|
||||||
// the point is to avoid thousands of small writes every frame if the cpu decides to
|
|
||||||
// access that region, anything higher than 8 you're guaranteed it comes from a service
|
|
||||||
auto interval = size <= 8 ? region : region & flush_interval;
|
|
||||||
if (flush_surface_id && surface_id != flush_surface_id) {
|
if (flush_surface_id && surface_id != flush_surface_id) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Small sizes imply that this most likely comes from the cpu, flush the entire region
|
||||||
|
// the point is to avoid thousands of small writes every frame if the cpu decides to
|
||||||
|
// access that region, anything higher than 8 you're guaranteed it comes from a service
|
||||||
|
const auto interval = size <= 8 ? region : region & flush_interval;
|
||||||
|
Surface& surface = slot_surfaces[surface_id];
|
||||||
|
ASSERT_MSG(surface.IsRegionValid(interval), "Region owner has invalid regions");
|
||||||
|
|
||||||
const DebugScope scope{runtime, Common::Vec4f{0.f, 0.f, 0.f, 1.f},
|
const DebugScope scope{runtime, Common::Vec4f{0.f, 0.f, 0.f, 1.f},
|
||||||
"RasterizerCache::FlushRegion (from {:#x} to {:#x})",
|
"RasterizerCache::FlushRegion (from {:#x} to {:#x})",
|
||||||
interval.lower(), interval.upper()};
|
interval.lower(), interval.upper()};
|
||||||
|
|
||||||
// Sanity check, this surface is the last one that marked this region dirty
|
SCOPE_EXIT({ flushed_intervals += interval; });
|
||||||
Surface& surface = slot_surfaces[surface_id];
|
if (surface.IsFill()) {
|
||||||
ASSERT(surface.IsRegionValid(interval));
|
|
||||||
|
|
||||||
if (surface.type == SurfaceType::Fill) {
|
|
||||||
DownloadFillSurface(surface, interval);
|
DownloadFillSurface(surface, interval);
|
||||||
flushed_intervals += interval;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1261,8 +1242,6 @@ void RasterizerCache<T>::FlushRegion(PAddr addr, u32 size, SurfaceId flush_surfa
|
||||||
}
|
}
|
||||||
DownloadSurface(surface, download_interval);
|
DownloadSurface(surface, download_interval);
|
||||||
}
|
}
|
||||||
|
|
||||||
flushed_intervals += interval;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset dirty regions
|
// Reset dirty regions
|
||||||
|
@ -1294,7 +1273,6 @@ void RasterizerCache<T>::InvalidateRegion(PAddr addr, u32 size, SurfaceId region
|
||||||
if (surface_id == region_owner_id) {
|
if (surface_id == region_owner_id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the CPU is invalidating this region we want to remove it
|
// If the CPU is invalidating this region we want to remove it
|
||||||
// to (likely) mark the memory pages as uncached
|
// to (likely) mark the memory pages as uncached
|
||||||
if (!region_owner_id && size <= 8) {
|
if (!region_owner_id && size <= 8) {
|
||||||
|
@ -1302,14 +1280,12 @@ void RasterizerCache<T>::InvalidateRegion(PAddr addr, u32 size, SurfaceId region
|
||||||
remove_surfaces.push_back(surface_id);
|
remove_surfaces.push_back(surface_id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const auto interval = surface.GetInterval() & invalid_interval;
|
||||||
surface.MarkInvalid(surface.GetInterval() & invalid_interval);
|
surface.MarkInvalid(interval);
|
||||||
|
if (!surface.IsFullyInvalid()) {
|
||||||
// If the surface has no salvageable data it should be removed
|
return;
|
||||||
// from the cache to avoid clogging the data structure.
|
|
||||||
if (surface.IsFullyInvalid()) {
|
|
||||||
remove_surfaces.push_back(surface_id);
|
|
||||||
}
|
}
|
||||||
|
remove_surfaces.push_back(surface_id);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (region_owner_id) {
|
if (region_owner_id) {
|
||||||
|
@ -1318,15 +1294,30 @@ void RasterizerCache<T>::InvalidateRegion(PAddr addr, u32 size, SurfaceId region
|
||||||
dirty_regions.erase(invalid_interval);
|
dirty_regions.erase(invalid_interval);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const SurfaceId remove_surface_id : remove_surfaces) {
|
for (const SurfaceId surface_id : remove_surfaces) {
|
||||||
UnregisterSurface(remove_surface_id);
|
UnregisterSurface(surface_id);
|
||||||
|
if (!slot_surfaces[surface_id].IsFill()) {
|
||||||
|
sentenced.emplace_back(surface_id, frame_tick);
|
||||||
|
} else {
|
||||||
|
slot_surfaces.erase(surface_id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
remove_surfaces.clear();
|
remove_surfaces.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
SurfaceId RasterizerCache<T>::CreateSurface(const SurfaceParams& params) {
|
SurfaceId RasterizerCache<T>::CreateSurface(const SurfaceParams& params) {
|
||||||
SurfaceId surface_id = slot_surfaces.insert(runtime, params);
|
const SurfaceId surface_id = [&] {
|
||||||
|
const auto it = std::find_if(sentenced.begin(), sentenced.end(), [&](const auto& pair) {
|
||||||
|
return slot_surfaces[pair.first] == params;
|
||||||
|
});
|
||||||
|
if (it != sentenced.end()) {
|
||||||
|
const SurfaceId surface_id = it->first;
|
||||||
|
sentenced.erase(it);
|
||||||
|
return surface_id;
|
||||||
|
}
|
||||||
|
return slot_surfaces.insert(runtime, params);
|
||||||
|
}();
|
||||||
Surface& surface = slot_surfaces[surface_id];
|
Surface& surface = slot_surfaces[surface_id];
|
||||||
surface.MarkInvalid(surface.GetInterval());
|
surface.MarkInvalid(surface.GetInterval());
|
||||||
return surface_id;
|
return surface_id;
|
||||||
|
@ -1368,8 +1359,6 @@ void RasterizerCache<T>::UnregisterSurface(SurfaceId surface_id) {
|
||||||
surfaces.erase(vector_it);
|
surfaces.erase(vector_it);
|
||||||
});
|
});
|
||||||
|
|
||||||
SCOPE_EXIT({ slot_surfaces.erase(surface_id); });
|
|
||||||
|
|
||||||
if (False(surface.flags & SurfaceFlagBits::Tracked)) {
|
if (False(surface.flags & SurfaceFlagBits::Tracked)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1383,7 +1372,7 @@ void RasterizerCache<T>::UnregisterSurface(SurfaceId surface_id) {
|
||||||
}
|
}
|
||||||
if (std::none_of(cube.face_ids.begin(), cube.face_ids.end(),
|
if (std::none_of(cube.face_ids.begin(), cube.face_ids.end(),
|
||||||
[](SurfaceId id) { return id; })) {
|
[](SurfaceId id) { return id; })) {
|
||||||
slot_surfaces.erase(cube.surface_id);
|
sentenced.emplace_back(cube.surface_id, frame_tick);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -1400,7 +1389,6 @@ void RasterizerCache<T>::UnregisterAll() {
|
||||||
}
|
}
|
||||||
texture_cube_cache.clear();
|
texture_cube_cache.clear();
|
||||||
remove_surfaces.clear();
|
remove_surfaces.clear();
|
||||||
runtime.Reset();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
|
|
|
@ -5,11 +5,13 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <list>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <boost/icl/interval_map.hpp>
|
#include <boost/icl/interval_map.hpp>
|
||||||
#include <tsl/robin_map.h>
|
#include <tsl/robin_map.h>
|
||||||
|
#include "video_core/rasterizer_cache/framebuffer_base.h"
|
||||||
#include "video_core/rasterizer_cache/sampler_params.h"
|
#include "video_core/rasterizer_cache/sampler_params.h"
|
||||||
#include "video_core/rasterizer_cache/surface_params.h"
|
#include "video_core/rasterizer_cache/surface_params.h"
|
||||||
#include "video_core/rasterizer_cache/texture_cube.h"
|
#include "video_core/rasterizer_cache/texture_cube.h"
|
||||||
|
@ -26,6 +28,10 @@ namespace Pica::Texture {
|
||||||
struct TextureInfo;
|
struct TextureInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace Settings {
|
||||||
|
enum class TextureFilter : u32;
|
||||||
|
}
|
||||||
|
|
||||||
namespace VideoCore {
|
namespace VideoCore {
|
||||||
|
|
||||||
enum class ScaleMatch {
|
enum class ScaleMatch {
|
||||||
|
@ -38,9 +44,8 @@ enum class MatchFlags {
|
||||||
Exact = 1 << 0, ///< Surface perfectly matches params
|
Exact = 1 << 0, ///< Surface perfectly matches params
|
||||||
SubRect = 1 << 1, ///< Surface encompasses params
|
SubRect = 1 << 1, ///< Surface encompasses params
|
||||||
Copy = 1 << 2, ///< Surface that can be used as a copy source
|
Copy = 1 << 2, ///< Surface that can be used as a copy source
|
||||||
Expand = 1 << 3, ///< Surface that can expand params
|
TexCopy = 1 << 3, ///< Surface that will match a display transfer "texture copy" parameters
|
||||||
TexCopy = 1 << 4, ///< Surface that will match a display transfer "texture copy" parameters
|
Reinterpret = 1 << 4, ///< Surface might have different pixel format.
|
||||||
Reinterpret = 1 << 5, ///< Surface might have different pixel format.
|
|
||||||
};
|
};
|
||||||
|
|
||||||
DECLARE_ENUM_FLAG_OPERATORS(MatchFlags);
|
DECLARE_ENUM_FLAG_OPERATORS(MatchFlags);
|
||||||
|
@ -66,11 +71,6 @@ class RasterizerCache {
|
||||||
using SurfaceRect_Tuple = std::pair<SurfaceId, Common::Rectangle<u32>>;
|
using SurfaceRect_Tuple = std::pair<SurfaceId, Common::Rectangle<u32>>;
|
||||||
using PageMap = boost::icl::interval_map<u32, int>;
|
using PageMap = boost::icl::interval_map<u32, int>;
|
||||||
|
|
||||||
struct RenderTargets {
|
|
||||||
SurfaceId color_id;
|
|
||||||
SurfaceId depth_id;
|
|
||||||
};
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit RasterizerCache(Memory::MemorySystem& memory, CustomTexManager& custom_tex_manager,
|
explicit RasterizerCache(Memory::MemorySystem& memory, CustomTexManager& custom_tex_manager,
|
||||||
Runtime& runtime, Pica::Regs& regs, RendererBase& renderer);
|
Runtime& runtime, Pica::Regs& regs, RendererBase& renderer);
|
||||||
|
@ -115,10 +115,7 @@ public:
|
||||||
Surface& GetTextureCube(const TextureCubeConfig& config);
|
Surface& GetTextureCube(const TextureCubeConfig& config);
|
||||||
|
|
||||||
/// Get the color and depth surfaces based on the framebuffer configuration
|
/// Get the color and depth surfaces based on the framebuffer configuration
|
||||||
Framebuffer GetFramebufferSurfaces(bool using_color_fb, bool using_depth_fb);
|
FramebufferHelper<T> GetFramebufferSurfaces(bool using_color_fb, bool using_depth_fb);
|
||||||
|
|
||||||
/// Marks the draw rectangle defined in framebuffer as invalid
|
|
||||||
void InvalidateFramebuffer(const Framebuffer& framebuffer);
|
|
||||||
|
|
||||||
/// Get a surface that matches a "texture copy" display transfer config
|
/// Get a surface that matches a "texture copy" display transfer config
|
||||||
SurfaceRect_Tuple GetTexCopySurface(const SurfaceParams& params);
|
SurfaceRect_Tuple GetTexCopySurface(const SurfaceParams& params);
|
||||||
|
@ -161,6 +158,12 @@ private:
|
||||||
SurfaceId FindMatch(const SurfaceParams& params, ScaleMatch match_scale_type,
|
SurfaceId FindMatch(const SurfaceParams& params, ScaleMatch match_scale_type,
|
||||||
std::optional<SurfaceInterval> validate_interval = std::nullopt);
|
std::optional<SurfaceInterval> validate_interval = std::nullopt);
|
||||||
|
|
||||||
|
/// Unregisters sentenced surfaces that have surpassed the destruction threshold.
|
||||||
|
void RunGarbageCollector();
|
||||||
|
|
||||||
|
/// Removes any framebuffers that reference the provided surface_id.
|
||||||
|
void RemoveFramebuffers(SurfaceId surface_id);
|
||||||
|
|
||||||
/// Transfers ownership of a memory region from src_surface to dest_surface
|
/// Transfers ownership of a memory region from src_surface to dest_surface
|
||||||
void DuplicateSurface(SurfaceId src_id, SurfaceId dst_id);
|
void DuplicateSurface(SurfaceId src_id, SurfaceId dst_id);
|
||||||
|
|
||||||
|
@ -209,15 +212,19 @@ private:
|
||||||
RendererBase& renderer;
|
RendererBase& renderer;
|
||||||
std::unordered_map<TextureCubeConfig, TextureCube> texture_cube_cache;
|
std::unordered_map<TextureCubeConfig, TextureCube> texture_cube_cache;
|
||||||
tsl::robin_pg_map<u64, std::vector<SurfaceId>, Common::IdentityHash<u64>> page_table;
|
tsl::robin_pg_map<u64, std::vector<SurfaceId>, Common::IdentityHash<u64>> page_table;
|
||||||
|
std::unordered_map<FramebufferParams, FramebufferId> framebuffers;
|
||||||
std::unordered_map<SamplerParams, SamplerId> samplers;
|
std::unordered_map<SamplerParams, SamplerId> samplers;
|
||||||
|
std::list<std::pair<SurfaceId, u64>> sentenced;
|
||||||
Common::SlotVector<Surface> slot_surfaces;
|
Common::SlotVector<Surface> slot_surfaces;
|
||||||
Common::SlotVector<Sampler> slot_samplers;
|
Common::SlotVector<Sampler> slot_samplers;
|
||||||
|
Common::SlotVector<Framebuffer> slot_framebuffers;
|
||||||
SurfaceMap dirty_regions;
|
SurfaceMap dirty_regions;
|
||||||
PageMap cached_pages;
|
PageMap cached_pages;
|
||||||
std::vector<SurfaceId> remove_surfaces;
|
std::vector<SurfaceId> remove_surfaces;
|
||||||
u32 resolution_scale_factor;
|
u32 resolution_scale_factor;
|
||||||
RenderTargets render_targets;
|
u64 frame_tick{};
|
||||||
bool use_filter;
|
FramebufferParams fb_params;
|
||||||
|
Settings::TextureFilter filter;
|
||||||
bool dump_textures;
|
bool dump_textures;
|
||||||
bool use_custom_textures;
|
bool use_custom_textures;
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,6 +10,7 @@ namespace VideoCore {
|
||||||
|
|
||||||
using SurfaceId = Common::SlotId;
|
using SurfaceId = Common::SlotId;
|
||||||
using SamplerId = Common::SlotId;
|
using SamplerId = Common::SlotId;
|
||||||
|
using FramebufferId = Common::SlotId;
|
||||||
|
|
||||||
/// Fake surface ID for null surfaces
|
/// Fake surface ID for null surfaces
|
||||||
constexpr SurfaceId NULL_SURFACE_ID{0};
|
constexpr SurfaceId NULL_SURFACE_ID{0};
|
||||||
|
|
|
@ -46,6 +46,10 @@ public:
|
||||||
/// Returns true if the surface contains a custom material with a normal map.
|
/// Returns true if the surface contains a custom material with a normal map.
|
||||||
bool HasNormalMap() const noexcept;
|
bool HasNormalMap() const noexcept;
|
||||||
|
|
||||||
|
bool IsFill() const noexcept {
|
||||||
|
return type == SurfaceType::Fill;
|
||||||
|
}
|
||||||
|
|
||||||
bool Overlaps(PAddr overlap_addr, size_t overlap_size) const noexcept {
|
bool Overlaps(PAddr overlap_addr, size_t overlap_size) const noexcept {
|
||||||
const PAddr overlap_end = overlap_addr + static_cast<PAddr>(overlap_size);
|
const PAddr overlap_end = overlap_addr + static_cast<PAddr>(overlap_size);
|
||||||
return addr < overlap_end && overlap_addr < end;
|
return addr < overlap_end && overlap_addr < end;
|
||||||
|
|
|
@ -34,15 +34,6 @@ bool SurfaceParams::CanReinterpret(const SurfaceParams& other_surface) {
|
||||||
GetSubRect(other_surface).right <= stride;
|
GetSubRect(other_surface).right <= stride;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SurfaceParams::CanExpand(const SurfaceParams& expanded_surface) const {
|
|
||||||
return pixel_format != PixelFormat::Invalid && pixel_format == expanded_surface.pixel_format &&
|
|
||||||
addr <= expanded_surface.end && expanded_surface.addr <= end &&
|
|
||||||
is_tiled == expanded_surface.is_tiled && stride == expanded_surface.stride &&
|
|
||||||
(std::max(expanded_surface.addr, addr) - std::min(expanded_surface.addr, addr)) %
|
|
||||||
BytesInPixels(stride * (is_tiled ? 8 : 1)) ==
|
|
||||||
0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SurfaceParams::CanTexCopy(const SurfaceParams& texcopy_params) const {
|
bool SurfaceParams::CanTexCopy(const SurfaceParams& texcopy_params) const {
|
||||||
const SurfaceInterval copy_interval = texcopy_params.GetInterval();
|
const SurfaceInterval copy_interval = texcopy_params.GetInterval();
|
||||||
if (pixel_format == PixelFormat::Invalid || addr > texcopy_params.addr ||
|
if (pixel_format == PixelFormat::Invalid || addr > texcopy_params.addr ||
|
||||||
|
|
|
@ -26,9 +26,6 @@ public:
|
||||||
/// Returns true if other_surface can be used for reinterpretion.
|
/// Returns true if other_surface can be used for reinterpretion.
|
||||||
bool CanReinterpret(const SurfaceParams& other_surface);
|
bool CanReinterpret(const SurfaceParams& other_surface);
|
||||||
|
|
||||||
/// Returns true if params can be expanded to match expanded_surface
|
|
||||||
bool CanExpand(const SurfaceParams& expanded_surface) const;
|
|
||||||
|
|
||||||
/// Returns true if params can be used for texcopy
|
/// Returns true if params can be used for texcopy
|
||||||
bool CanTexCopy(const SurfaceParams& texcopy_params) const;
|
bool CanTexCopy(const SurfaceParams& texcopy_params) const;
|
||||||
|
|
||||||
|
@ -56,6 +53,10 @@ public:
|
||||||
/// Returns a string identifier of the params object
|
/// Returns a string identifier of the params object
|
||||||
std::string DebugName(bool scaled, bool custom = false) const noexcept;
|
std::string DebugName(bool scaled, bool custom = false) const noexcept;
|
||||||
|
|
||||||
|
bool operator==(const SurfaceParams& other) const noexcept {
|
||||||
|
return std::memcmp(this, &other, sizeof(SurfaceParams)) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] SurfaceInterval GetInterval() const noexcept {
|
[[nodiscard]] SurfaceInterval GetInterval() const noexcept {
|
||||||
return SurfaceInterval{addr, end};
|
return SurfaceInterval{addr, end};
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,6 +67,7 @@ struct StagingData {
|
||||||
};
|
};
|
||||||
|
|
||||||
class SurfaceParams;
|
class SurfaceParams;
|
||||||
|
struct FramebufferParams;
|
||||||
|
|
||||||
u32 MipLevels(u32 width, u32 height, u32 max_level);
|
u32 MipLevels(u32 width, u32 height, u32 max_level);
|
||||||
|
|
||||||
|
|
|
@ -31,9 +31,7 @@ struct RendererSettings {
|
||||||
std::function<void()> screenshot_complete_callback;
|
std::function<void()> screenshot_complete_callback;
|
||||||
Layout::FramebufferLayout screenshot_framebuffer_layout;
|
Layout::FramebufferLayout screenshot_framebuffer_layout;
|
||||||
// Renderer
|
// Renderer
|
||||||
std::atomic_bool texture_filter_update_requested{false};
|
|
||||||
std::atomic_bool bg_color_update_requested{false};
|
std::atomic_bool bg_color_update_requested{false};
|
||||||
std::atomic_bool sampler_update_requested{false};
|
|
||||||
std::atomic_bool shader_update_requested{false};
|
std::atomic_bool shader_update_requested{false};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include "core/frontend/emu_window.h"
|
#include "core/frontend/emu_window.h"
|
||||||
#include "video_core/renderer_opengl/frame_dumper_opengl.h"
|
#include "video_core/renderer_opengl/frame_dumper_opengl.h"
|
||||||
#include "video_core/renderer_opengl/renderer_opengl.h"
|
#include "video_core/renderer_opengl/gl_texture_mailbox.h"
|
||||||
|
|
||||||
namespace OpenGL {
|
namespace OpenGL {
|
||||||
|
|
||||||
|
|
|
@ -386,21 +386,20 @@ bool RasterizerOpenGL::Draw(bool accelerate, bool is_indexed) {
|
||||||
(write_depth_fb || regs.framebuffer.output_merger.depth_test_enable != 0 ||
|
(write_depth_fb || regs.framebuffer.output_merger.depth_test_enable != 0 ||
|
||||||
(has_stencil && state.stencil.test_enabled));
|
(has_stencil && state.stencil.test_enabled));
|
||||||
|
|
||||||
const Framebuffer framebuffer =
|
const auto fb_helper = res_cache.GetFramebufferSurfaces(using_color_fb, using_depth_fb);
|
||||||
res_cache.GetFramebufferSurfaces(using_color_fb, using_depth_fb);
|
const Framebuffer* framebuffer = fb_helper.Framebuffer();
|
||||||
const bool has_color = framebuffer.HasAttachment(SurfaceType::Color);
|
if (!framebuffer->color_id && framebuffer->shadow_rendering) {
|
||||||
if (!has_color && shadow_rendering) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bind the framebuffer surfaces
|
// Bind the framebuffer surfaces
|
||||||
if (shadow_rendering) {
|
if (shadow_rendering) {
|
||||||
state.image_shadow_buffer = framebuffer.Attachment(SurfaceType::Color);
|
state.image_shadow_buffer = framebuffer->Attachment(SurfaceType::Color);
|
||||||
}
|
}
|
||||||
state.draw.draw_framebuffer = framebuffer.Handle();
|
state.draw.draw_framebuffer = framebuffer->Handle();
|
||||||
|
|
||||||
// Sync the viewport
|
// Sync the viewport
|
||||||
const auto viewport = framebuffer.Viewport();
|
const auto viewport = fb_helper.Viewport();
|
||||||
state.viewport.x = static_cast<GLint>(viewport.x);
|
state.viewport.x = static_cast<GLint>(viewport.x);
|
||||||
state.viewport.y = static_cast<GLint>(viewport.y);
|
state.viewport.y = static_cast<GLint>(viewport.y);
|
||||||
state.viewport.width = static_cast<GLsizei>(viewport.width);
|
state.viewport.width = static_cast<GLsizei>(viewport.width);
|
||||||
|
@ -408,21 +407,15 @@ bool RasterizerOpenGL::Draw(bool accelerate, bool is_indexed) {
|
||||||
|
|
||||||
// Viewport can have negative offsets or larger dimensions than our framebuffer sub-rect.
|
// Viewport can have negative offsets or larger dimensions than our framebuffer sub-rect.
|
||||||
// Enable scissor test to prevent drawing outside of the framebuffer region
|
// Enable scissor test to prevent drawing outside of the framebuffer region
|
||||||
const auto draw_rect = framebuffer.DrawRect();
|
const auto draw_rect = fb_helper.DrawRect();
|
||||||
state.scissor.enabled = true;
|
state.scissor.enabled = true;
|
||||||
state.scissor.x = draw_rect.left;
|
state.scissor.x = draw_rect.left;
|
||||||
state.scissor.y = draw_rect.bottom;
|
state.scissor.y = draw_rect.bottom;
|
||||||
state.scissor.width = draw_rect.GetWidth();
|
state.scissor.width = draw_rect.GetWidth();
|
||||||
state.scissor.height = draw_rect.GetHeight();
|
state.scissor.height = draw_rect.GetHeight();
|
||||||
|
|
||||||
const int res_scale = static_cast<int>(framebuffer.ResolutionScale());
|
|
||||||
if (uniform_block_data.data.framebuffer_scale != res_scale) {
|
|
||||||
uniform_block_data.data.framebuffer_scale = res_scale;
|
|
||||||
uniform_block_data.dirty = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update scissor uniforms
|
// Update scissor uniforms
|
||||||
const auto [scissor_x1, scissor_y2, scissor_x2, scissor_y1] = framebuffer.Scissor();
|
const auto [scissor_x1, scissor_y2, scissor_x2, scissor_y1] = fb_helper.Scissor();
|
||||||
if (uniform_block_data.data.scissor_x1 != scissor_x1 ||
|
if (uniform_block_data.data.scissor_x1 != scissor_x1 ||
|
||||||
uniform_block_data.data.scissor_x2 != scissor_x2 ||
|
uniform_block_data.data.scissor_x2 != scissor_x2 ||
|
||||||
uniform_block_data.data.scissor_y1 != scissor_y1 ||
|
uniform_block_data.data.scissor_y1 != scissor_y1 ||
|
||||||
|
@ -486,13 +479,12 @@ bool RasterizerOpenGL::Draw(bool accelerate, bool is_indexed) {
|
||||||
GL_TEXTURE_UPDATE_BARRIER_BIT | GL_FRAMEBUFFER_BARRIER_BIT);
|
GL_TEXTURE_UPDATE_BARRIER_BIT | GL_FRAMEBUFFER_BARRIER_BIT);
|
||||||
}
|
}
|
||||||
|
|
||||||
res_cache.InvalidateFramebuffer(framebuffer);
|
|
||||||
use_custom_normal = false;
|
use_custom_normal = false;
|
||||||
|
|
||||||
return succeeded;
|
return succeeded;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RasterizerOpenGL::SyncTextureUnits(const Framebuffer& framebuffer) {
|
void RasterizerOpenGL::SyncTextureUnits(const Framebuffer* framebuffer) {
|
||||||
using TextureType = Pica::TexturingRegs::TextureConfig::TextureType;
|
using TextureType = Pica::TexturingRegs::TextureConfig::TextureType;
|
||||||
|
|
||||||
const auto pica_textures = regs.texturing.GetTextures();
|
const auto pica_textures = regs.texturing.GetTextures();
|
||||||
|
@ -603,27 +595,15 @@ void RasterizerOpenGL::BindMaterial(u32 texture_index, Surface& surface) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RasterizerOpenGL::IsFeedbackLoop(u32 texture_index, const Framebuffer& framebuffer,
|
bool RasterizerOpenGL::IsFeedbackLoop(u32 texture_index, const Framebuffer* framebuffer,
|
||||||
Surface& surface) {
|
Surface& surface) {
|
||||||
const GLuint color_attachment = framebuffer.Attachment(SurfaceType::Color);
|
const GLuint color_attachment = framebuffer->Attachment(SurfaceType::Color);
|
||||||
const bool is_feedback_loop = color_attachment == surface.Handle();
|
const bool is_feedback_loop = color_attachment == surface.Handle();
|
||||||
if (!is_feedback_loop) {
|
if (!is_feedback_loop) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make a temporary copy of the framebuffer to sample from
|
state.texture_units[texture_index].texture_2d = surface.CopyHandle();
|
||||||
Surface temp_surface{runtime, framebuffer.ColorParams()};
|
|
||||||
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_surface.GetScaledWidth(), temp_surface.GetScaledHeight()},
|
|
||||||
};
|
|
||||||
runtime.CopyTextures(surface, temp_surface, copy);
|
|
||||||
state.texture_units[texture_index].texture_2d = temp_surface.Handle();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -93,7 +93,7 @@ private:
|
||||||
void SyncAndUploadLUTsLF();
|
void SyncAndUploadLUTsLF();
|
||||||
|
|
||||||
/// Syncs all enabled PICA texture units
|
/// Syncs all enabled PICA texture units
|
||||||
void SyncTextureUnits(const Framebuffer& framebuffer);
|
void SyncTextureUnits(const Framebuffer* framebuffer);
|
||||||
|
|
||||||
/// Binds the PICA shadow cube required for shadow mapping
|
/// Binds the PICA shadow cube required for shadow mapping
|
||||||
void BindShadowCube(const Pica::TexturingRegs::FullTextureConfig& texture);
|
void BindShadowCube(const Pica::TexturingRegs::FullTextureConfig& texture);
|
||||||
|
@ -102,7 +102,7 @@ private:
|
||||||
void BindTextureCube(const Pica::TexturingRegs::FullTextureConfig& texture);
|
void BindTextureCube(const Pica::TexturingRegs::FullTextureConfig& texture);
|
||||||
|
|
||||||
/// Makes a temporary copy of the framebuffer if a feedback loop is detected
|
/// Makes a temporary copy of the framebuffer if a feedback loop is detected
|
||||||
bool IsFeedbackLoop(u32 texture_index, const Framebuffer& framebuffer, Surface& surface);
|
bool IsFeedbackLoop(u32 texture_index, const Framebuffer* framebuffer, Surface& surface);
|
||||||
|
|
||||||
/// Unbinds all special texture unit 0 texture configurations
|
/// Unbinds all special texture unit 0 texture configurations
|
||||||
void UnbindSpecial();
|
void UnbindSpecial();
|
||||||
|
|
|
@ -3,8 +3,6 @@
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#include <glad/glad.h>
|
#include <glad/glad.h>
|
||||||
#include "common/common_funcs.h"
|
|
||||||
#include "common/logging/log.h"
|
|
||||||
#include "video_core/renderer_opengl/gl_state.h"
|
#include "video_core/renderer_opengl/gl_state.h"
|
||||||
#include "video_core/renderer_opengl/gl_vars.h"
|
#include "video_core/renderer_opengl/gl_vars.h"
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,194 @@
|
||||||
|
// Copyright 2023 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "video_core/renderer_opengl/gl_state.h"
|
||||||
|
#include "video_core/renderer_opengl/gl_texture_mailbox.h"
|
||||||
|
|
||||||
|
namespace OpenGL {
|
||||||
|
|
||||||
|
OGLTextureMailbox::OGLTextureMailbox(bool has_debug_tool_) : has_debug_tool{has_debug_tool_} {
|
||||||
|
for (auto& frame : swap_chain) {
|
||||||
|
free_queue.push(&frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OGLTextureMailbox::~OGLTextureMailbox() {
|
||||||
|
// Lock the mutex and clear out the present and free_queues and notify any people who are
|
||||||
|
// blocked to prevent deadlock on shutdown
|
||||||
|
std::scoped_lock lock(swap_chain_lock);
|
||||||
|
free_queue = {};
|
||||||
|
present_queue.clear();
|
||||||
|
present_cv.notify_all();
|
||||||
|
free_cv.notify_all();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OGLTextureMailbox::ReloadPresentFrame(Frontend::Frame* frame, u32 height, u32 width) {
|
||||||
|
frame->present.Release();
|
||||||
|
frame->present.Create();
|
||||||
|
GLint previous_draw_fbo{};
|
||||||
|
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &previous_draw_fbo);
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, frame->present.handle);
|
||||||
|
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
|
||||||
|
frame->color.handle);
|
||||||
|
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
|
||||||
|
LOG_CRITICAL(Render_OpenGL, "Failed to recreate present FBO!");
|
||||||
|
}
|
||||||
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, previous_draw_fbo);
|
||||||
|
frame->color_reloaded = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OGLTextureMailbox::ReloadRenderFrame(Frontend::Frame* frame, u32 width, u32 height) {
|
||||||
|
OpenGLState prev_state = OpenGLState::GetCurState();
|
||||||
|
OpenGLState state = OpenGLState::GetCurState();
|
||||||
|
|
||||||
|
// Recreate the color texture attachment
|
||||||
|
frame->color.Release();
|
||||||
|
frame->color.Create();
|
||||||
|
state.renderbuffer = frame->color.handle;
|
||||||
|
state.Apply();
|
||||||
|
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, width, height);
|
||||||
|
|
||||||
|
// Recreate the FBO for the render target
|
||||||
|
frame->render.Release();
|
||||||
|
frame->render.Create();
|
||||||
|
state.draw.read_framebuffer = frame->render.handle;
|
||||||
|
state.draw.draw_framebuffer = frame->render.handle;
|
||||||
|
state.Apply();
|
||||||
|
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
|
||||||
|
frame->color.handle);
|
||||||
|
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
|
||||||
|
LOG_CRITICAL(Render_OpenGL, "Failed to recreate render FBO!");
|
||||||
|
}
|
||||||
|
prev_state.Apply();
|
||||||
|
frame->width = width;
|
||||||
|
frame->height = height;
|
||||||
|
frame->color_reloaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Frontend::Frame* OGLTextureMailbox::GetRenderFrame() {
|
||||||
|
std::unique_lock lock{swap_chain_lock};
|
||||||
|
|
||||||
|
// If theres no free frames, we will reuse the oldest render frame
|
||||||
|
if (free_queue.empty()) {
|
||||||
|
auto frame = present_queue.back();
|
||||||
|
present_queue.pop_back();
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
Frontend::Frame* frame = free_queue.front();
|
||||||
|
free_queue.pop();
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OGLTextureMailbox::ReleaseRenderFrame(Frontend::Frame* frame) {
|
||||||
|
std::unique_lock lock{swap_chain_lock};
|
||||||
|
present_queue.push_front(frame);
|
||||||
|
present_cv.notify_one();
|
||||||
|
|
||||||
|
DebugNotifyNextFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OGLTextureMailbox::LoadPresentFrame() {
|
||||||
|
// Free the previous frame and add it back to the free queue
|
||||||
|
if (previous_frame) {
|
||||||
|
free_queue.push(previous_frame);
|
||||||
|
free_cv.notify_one();
|
||||||
|
}
|
||||||
|
|
||||||
|
// The newest entries are pushed to the front of the queue
|
||||||
|
Frontend::Frame* frame = present_queue.front();
|
||||||
|
present_queue.pop_front();
|
||||||
|
// Remove all old entries from the present queue and move them back to the free_queue
|
||||||
|
for (auto f : present_queue) {
|
||||||
|
free_queue.push(f);
|
||||||
|
}
|
||||||
|
present_queue.clear();
|
||||||
|
previous_frame = frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
Frontend::Frame* OGLTextureMailbox::TryGetPresentFrame(int timeout_ms) {
|
||||||
|
DebugWaitForNextFrame();
|
||||||
|
|
||||||
|
std::unique_lock lock{swap_chain_lock};
|
||||||
|
// Wait for new entries in the present_queue
|
||||||
|
present_cv.wait_for(lock, std::chrono::milliseconds(timeout_ms),
|
||||||
|
[&] { return !present_queue.empty(); });
|
||||||
|
if (present_queue.empty()) {
|
||||||
|
// Timed out waiting for a frame to draw so return the previous frame
|
||||||
|
return previous_frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
LoadPresentFrame();
|
||||||
|
return previous_frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OGLTextureMailbox::DebugNotifyNextFrame() {
|
||||||
|
if (!has_debug_tool) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
frame_for_debug++;
|
||||||
|
std::scoped_lock lock{debug_synch_mutex};
|
||||||
|
debug_synch_condition.notify_one();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OGLTextureMailbox::DebugWaitForNextFrame() {
|
||||||
|
if (!has_debug_tool) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const int last_frame = frame_for_debug;
|
||||||
|
std::unique_lock lock{debug_synch_mutex};
|
||||||
|
debug_synch_condition.wait(lock, [this, last_frame] { return frame_for_debug > last_frame; });
|
||||||
|
}
|
||||||
|
|
||||||
|
Frontend::Frame* OGLVideoDumpingMailbox::GetRenderFrame() {
|
||||||
|
std::unique_lock lock{swap_chain_lock};
|
||||||
|
|
||||||
|
// If theres no free frames, we will wait until one shows up
|
||||||
|
if (free_queue.empty()) {
|
||||||
|
free_cv.wait(lock, [&] { return (!free_queue.empty() || quit); });
|
||||||
|
if (quit) {
|
||||||
|
throw OGLTextureMailboxException("VideoDumpingMailbox quitting");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (free_queue.empty()) {
|
||||||
|
LOG_CRITICAL(Render_OpenGL, "Could not get free frame");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Frontend::Frame* frame = free_queue.front();
|
||||||
|
free_queue.pop();
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OGLVideoDumpingMailbox::LoadPresentFrame() {
|
||||||
|
// Free the previous frame and add it back to the free queue
|
||||||
|
if (previous_frame) {
|
||||||
|
free_queue.push(previous_frame);
|
||||||
|
free_cv.notify_one();
|
||||||
|
}
|
||||||
|
|
||||||
|
Frontend::Frame* frame = present_queue.back();
|
||||||
|
present_queue.pop_back();
|
||||||
|
previous_frame = frame;
|
||||||
|
|
||||||
|
// Do not remove entries from the present_queue, as video dumping would require
|
||||||
|
// that we preserve all frames
|
||||||
|
}
|
||||||
|
|
||||||
|
Frontend::Frame* OGLVideoDumpingMailbox::TryGetPresentFrame(int timeout_ms) {
|
||||||
|
std::unique_lock lock{swap_chain_lock};
|
||||||
|
// Wait for new entries in the present_queue
|
||||||
|
present_cv.wait_for(lock, std::chrono::milliseconds(timeout_ms),
|
||||||
|
[&] { return !present_queue.empty(); });
|
||||||
|
if (present_queue.empty()) {
|
||||||
|
// Timed out waiting for a frame
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
LoadPresentFrame();
|
||||||
|
return previous_frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace OpenGL
|
|
@ -0,0 +1,92 @@
|
||||||
|
// Copyright 2023 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <deque>
|
||||||
|
#include <mutex>
|
||||||
|
#include <queue>
|
||||||
|
|
||||||
|
#include "core/frontend/emu_window.h"
|
||||||
|
#include "video_core/renderer_opengl/gl_resource_manager.h"
|
||||||
|
|
||||||
|
namespace Frontend {
|
||||||
|
struct Frame {
|
||||||
|
u32 width{}; ///< Width of the frame (to detect resize)
|
||||||
|
u32 height{}; ///< Height of the frame
|
||||||
|
bool color_reloaded = false; ///< Texture attachment was recreated (ie: resized)
|
||||||
|
OpenGL::OGLRenderbuffer color{}; ///< Buffer shared between the render/present FBO
|
||||||
|
OpenGL::OGLFramebuffer render{}; ///< FBO created on the render thread
|
||||||
|
OpenGL::OGLFramebuffer present{}; ///< FBO created on the present thread
|
||||||
|
GLsync render_fence{}; ///< Fence created on the render thread
|
||||||
|
GLsync present_fence{}; ///< Fence created on the presentation thread
|
||||||
|
};
|
||||||
|
} // namespace Frontend
|
||||||
|
|
||||||
|
namespace OpenGL {
|
||||||
|
|
||||||
|
// If the size of this is too small, it ends up creating a soft cap on FPS as the renderer will have
|
||||||
|
// to wait on available presentation frames. There doesn't seem to be much of a downside to a larger
|
||||||
|
// number but 9 swap textures at 60FPS presentation allows for 800% speed so thats probably fine
|
||||||
|
#ifdef ANDROID
|
||||||
|
// Reduce the size of swap_chain, since the UI only allows upto 200% speed.
|
||||||
|
constexpr std::size_t SWAP_CHAIN_SIZE = 6;
|
||||||
|
#else
|
||||||
|
constexpr std::size_t SWAP_CHAIN_SIZE = 9;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class OGLTextureMailbox : public Frontend::TextureMailbox {
|
||||||
|
public:
|
||||||
|
explicit OGLTextureMailbox(bool has_debug_tool = false);
|
||||||
|
~OGLTextureMailbox() override;
|
||||||
|
|
||||||
|
void ReloadPresentFrame(Frontend::Frame* frame, u32 height, u32 width) override;
|
||||||
|
void ReloadRenderFrame(Frontend::Frame* frame, u32 width, u32 height) override;
|
||||||
|
void ReleaseRenderFrame(Frontend::Frame* frame) override;
|
||||||
|
|
||||||
|
Frontend::Frame* GetRenderFrame() override;
|
||||||
|
Frontend::Frame* TryGetPresentFrame(int timeout_ms) override;
|
||||||
|
|
||||||
|
/// This is virtual as it is to be overriden in OGLVideoDumpingMailbox below.
|
||||||
|
virtual void LoadPresentFrame();
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// Signal that a new frame is available (called from GPU thread)
|
||||||
|
void DebugNotifyNextFrame();
|
||||||
|
|
||||||
|
/// Wait for a new frame to be available (called from presentation thread)
|
||||||
|
void DebugWaitForNextFrame();
|
||||||
|
|
||||||
|
public:
|
||||||
|
std::mutex swap_chain_lock;
|
||||||
|
std::condition_variable free_cv;
|
||||||
|
std::condition_variable present_cv;
|
||||||
|
std::array<Frontend::Frame, SWAP_CHAIN_SIZE> swap_chain{};
|
||||||
|
std::queue<Frontend::Frame*> free_queue{};
|
||||||
|
std::deque<Frontend::Frame*> present_queue{};
|
||||||
|
Frontend::Frame* previous_frame = nullptr;
|
||||||
|
std::mutex debug_synch_mutex;
|
||||||
|
std::condition_variable debug_synch_condition;
|
||||||
|
std::atomic_int frame_for_debug{};
|
||||||
|
const bool has_debug_tool; ///< When true, using a GPU debugger, so keep frames in lock-step
|
||||||
|
};
|
||||||
|
|
||||||
|
class OGLTextureMailboxException : public std::runtime_error {
|
||||||
|
public:
|
||||||
|
using std::runtime_error::runtime_error;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// This mailbox is different in that it will never discard rendered frames
|
||||||
|
class OGLVideoDumpingMailbox : public OGLTextureMailbox {
|
||||||
|
public:
|
||||||
|
void LoadPresentFrame() override;
|
||||||
|
Frontend::Frame* GetRenderFrame() override;
|
||||||
|
Frontend::Frame* TryGetPresentFrame(int timeout_ms) override;
|
||||||
|
|
||||||
|
public:
|
||||||
|
bool quit = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace OpenGL
|
|
@ -5,10 +5,10 @@
|
||||||
#include "common/scope_exit.h"
|
#include "common/scope_exit.h"
|
||||||
#include "common/settings.h"
|
#include "common/settings.h"
|
||||||
#include "video_core/custom_textures/material.h"
|
#include "video_core/custom_textures/material.h"
|
||||||
#include "video_core/regs.h"
|
|
||||||
#include "video_core/renderer_base.h"
|
#include "video_core/renderer_base.h"
|
||||||
#include "video_core/renderer_opengl/gl_driver.h"
|
#include "video_core/renderer_opengl/gl_driver.h"
|
||||||
#include "video_core/renderer_opengl/gl_state.h"
|
#include "video_core/renderer_opengl/gl_state.h"
|
||||||
|
#include "video_core/renderer_opengl/gl_texture_mailbox.h"
|
||||||
#include "video_core/renderer_opengl/gl_texture_runtime.h"
|
#include "video_core/renderer_opengl/gl_texture_runtime.h"
|
||||||
#include "video_core/renderer_opengl/pica_to_gl.h"
|
#include "video_core/renderer_opengl/pica_to_gl.h"
|
||||||
|
|
||||||
|
@ -22,6 +22,8 @@ using VideoCore::SurfaceFlagBits;
|
||||||
using VideoCore::SurfaceType;
|
using VideoCore::SurfaceType;
|
||||||
using VideoCore::TextureType;
|
using VideoCore::TextureType;
|
||||||
|
|
||||||
|
constexpr GLenum TEMP_UNIT = GL_TEXTURE15;
|
||||||
|
|
||||||
constexpr FormatTuple DEFAULT_TUPLE = {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE};
|
constexpr FormatTuple DEFAULT_TUPLE = {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE};
|
||||||
|
|
||||||
static constexpr std::array<FormatTuple, 4> DEPTH_TUPLES = {{
|
static constexpr std::array<FormatTuple, 4> DEPTH_TUPLES = {{
|
||||||
|
@ -58,13 +60,6 @@ static constexpr std::array<FormatTuple, 8> CUSTOM_TUPLES = {{
|
||||||
{GL_COMPRESSED_RGBA_ASTC_8x6, GL_COMPRESSED_RGBA_ASTC_8x6, GL_UNSIGNED_BYTE},
|
{GL_COMPRESSED_RGBA_ASTC_8x6, GL_COMPRESSED_RGBA_ASTC_8x6, GL_UNSIGNED_BYTE},
|
||||||
}};
|
}};
|
||||||
|
|
||||||
struct FramebufferInfo {
|
|
||||||
GLuint color;
|
|
||||||
GLuint depth;
|
|
||||||
u32 color_level;
|
|
||||||
u32 depth_level;
|
|
||||||
};
|
|
||||||
|
|
||||||
[[nodiscard]] GLbitfield MakeBufferMask(SurfaceType type) {
|
[[nodiscard]] GLbitfield MakeBufferMask(SurfaceType type) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case SurfaceType::Color:
|
case SurfaceType::Color:
|
||||||
|
@ -128,9 +123,8 @@ TextureRuntime::TextureRuntime(const Driver& driver_, VideoCore::RendererBase& r
|
||||||
|
|
||||||
TextureRuntime::~TextureRuntime() = default;
|
TextureRuntime::~TextureRuntime() = default;
|
||||||
|
|
||||||
void TextureRuntime::Reset() {
|
u32 TextureRuntime::RemoveThreshold() {
|
||||||
alloc_cache.clear();
|
return SWAP_CHAIN_SIZE;
|
||||||
framebuffer_cache.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TextureRuntime::NeedsConversion(VideoCore::PixelFormat pixel_format) const {
|
bool TextureRuntime::NeedsConversion(VideoCore::PixelFormat pixel_format) const {
|
||||||
|
@ -151,6 +145,10 @@ VideoCore::StagingData TextureRuntime::FindStaging(u32 size, bool upload) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const FormatTuple& TextureRuntime::GetFormatTuple(PixelFormat pixel_format) const {
|
const FormatTuple& TextureRuntime::GetFormatTuple(PixelFormat pixel_format) const {
|
||||||
|
if (pixel_format == PixelFormat::Invalid) {
|
||||||
|
return DEFAULT_TUPLE;
|
||||||
|
}
|
||||||
|
|
||||||
const auto type = GetFormatType(pixel_format);
|
const auto type = GetFormatType(pixel_format);
|
||||||
const std::size_t format_index = static_cast<std::size_t>(pixel_format);
|
const std::size_t format_index = static_cast<std::size_t>(pixel_format);
|
||||||
|
|
||||||
|
@ -171,74 +169,6 @@ const FormatTuple& TextureRuntime::GetFormatTuple(VideoCore::CustomPixelFormat p
|
||||||
return CUSTOM_TUPLES[format_index];
|
return CUSTOM_TUPLES[format_index];
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextureRuntime::Recycle(const HostTextureTag tag, Allocation&& alloc) {
|
|
||||||
alloc_cache.emplace(tag, std::move(alloc));
|
|
||||||
}
|
|
||||||
|
|
||||||
Allocation TextureRuntime::Allocate(const VideoCore::SurfaceParams& params,
|
|
||||||
const VideoCore::Material* material) {
|
|
||||||
const GLenum target = params.texture_type == VideoCore::TextureType::CubeMap
|
|
||||||
? GL_TEXTURE_CUBE_MAP
|
|
||||||
: GL_TEXTURE_2D;
|
|
||||||
const bool is_custom = material != nullptr;
|
|
||||||
const bool has_normal = material && material->Map(MapType::Normal);
|
|
||||||
const auto& tuple =
|
|
||||||
is_custom ? GetFormatTuple(params.custom_format) : GetFormatTuple(params.pixel_format);
|
|
||||||
const HostTextureTag key = {
|
|
||||||
.width = params.width,
|
|
||||||
.height = params.height,
|
|
||||||
.levels = params.levels,
|
|
||||||
.res_scale = params.res_scale,
|
|
||||||
.tuple = tuple,
|
|
||||||
.type = params.texture_type,
|
|
||||||
.is_custom = is_custom,
|
|
||||||
.has_normal = has_normal,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (auto it = alloc_cache.find(key); it != alloc_cache.end()) {
|
|
||||||
auto alloc{std::move(it->second)};
|
|
||||||
alloc_cache.erase(it);
|
|
||||||
return alloc;
|
|
||||||
}
|
|
||||||
|
|
||||||
const GLuint old_tex = OpenGLState::GetCurState().texture_units[0].texture_2d;
|
|
||||||
glActiveTexture(GL_TEXTURE0);
|
|
||||||
|
|
||||||
std::array<OGLTexture, 3> textures{};
|
|
||||||
std::array<GLuint, 3> handles{};
|
|
||||||
|
|
||||||
textures[0] = MakeHandle(target, params.width, params.height, params.levels, tuple,
|
|
||||||
params.DebugName(false));
|
|
||||||
handles.fill(textures[0].handle);
|
|
||||||
|
|
||||||
if (params.res_scale != 1) {
|
|
||||||
const u32 scaled_width = is_custom ? params.width : params.GetScaledWidth();
|
|
||||||
const u32 scaled_height = is_custom ? params.height : params.GetScaledHeight();
|
|
||||||
const auto& scaled_tuple = is_custom ? GetFormatTuple(PixelFormat::RGBA8) : tuple;
|
|
||||||
textures[1] = MakeHandle(target, scaled_width, scaled_height, params.levels, scaled_tuple,
|
|
||||||
params.DebugName(true, is_custom));
|
|
||||||
handles[1] = textures[1].handle;
|
|
||||||
}
|
|
||||||
if (has_normal) {
|
|
||||||
textures[2] = MakeHandle(target, params.width, params.height, params.levels, tuple,
|
|
||||||
params.DebugName(true, is_custom));
|
|
||||||
handles[2] = textures[2].handle;
|
|
||||||
}
|
|
||||||
|
|
||||||
glBindTexture(GL_TEXTURE_2D, old_tex);
|
|
||||||
|
|
||||||
return Allocation{
|
|
||||||
.textures = std::move(textures),
|
|
||||||
.handles = std::move(handles),
|
|
||||||
.tuple = tuple,
|
|
||||||
.width = params.width,
|
|
||||||
.height = params.height,
|
|
||||||
.levels = params.levels,
|
|
||||||
.res_scale = params.res_scale,
|
|
||||||
.is_custom = is_custom,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TextureRuntime::Reinterpret(Surface& source, Surface& dest,
|
bool TextureRuntime::Reinterpret(Surface& source, Surface& dest,
|
||||||
const VideoCore::TextureBlit& blit) {
|
const VideoCore::TextureBlit& blit) {
|
||||||
const PixelFormat src_format = source.pixel_format;
|
const PixelFormat src_format = source.pixel_format;
|
||||||
|
@ -353,40 +283,90 @@ void TextureRuntime::GenerateMipmaps(Surface& surface) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceParams& params)
|
Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceParams& params)
|
||||||
: SurfaceBase{params}, driver{&runtime_.GetDriver()}, runtime{&runtime_} {
|
: SurfaceBase{params}, driver{&runtime_.GetDriver()}, runtime{&runtime_},
|
||||||
|
tuple{runtime->GetFormatTuple(pixel_format)} {
|
||||||
if (pixel_format == PixelFormat::Invalid) {
|
if (pixel_format == PixelFormat::Invalid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
alloc = runtime->Allocate(params);
|
glActiveTexture(TEMP_UNIT);
|
||||||
|
const GLenum target =
|
||||||
|
texture_type == VideoCore::TextureType::CubeMap ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D;
|
||||||
|
|
||||||
|
textures[0] = MakeHandle(target, width, height, levels, tuple, DebugName(false));
|
||||||
|
if (res_scale != 1) {
|
||||||
|
textures[1] = MakeHandle(target, GetScaledWidth(), GetScaledHeight(), levels, tuple,
|
||||||
|
DebugName(true, false));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Surface::~Surface() {
|
Surface::Surface(TextureRuntime& runtime, const VideoCore::SurfaceBase& surface,
|
||||||
if (pixel_format == PixelFormat::Invalid || !alloc) {
|
const VideoCore::Material* mat)
|
||||||
|
: SurfaceBase{surface}, tuple{runtime.GetFormatTuple(mat->format)} {
|
||||||
|
if (mat && !driver->IsCustomFormatSupported(mat->format)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
runtime->Recycle(MakeTag(), std::move(alloc));
|
|
||||||
|
glActiveTexture(TEMP_UNIT);
|
||||||
|
const GLenum target =
|
||||||
|
texture_type == VideoCore::TextureType::CubeMap ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D;
|
||||||
|
|
||||||
|
custom_format = mat->format;
|
||||||
|
material = mat;
|
||||||
|
|
||||||
|
textures[0] = MakeHandle(target, mat->width, mat->height, levels, tuple, DebugName(false));
|
||||||
|
if (res_scale != 1) {
|
||||||
|
textures[1] = MakeHandle(target, mat->width, mat->height, levels, DEFAULT_TUPLE,
|
||||||
|
DebugName(true, true));
|
||||||
|
}
|
||||||
|
const bool has_normal = mat->Map(MapType::Normal);
|
||||||
|
if (has_normal) {
|
||||||
|
textures[2] =
|
||||||
|
MakeHandle(target, mat->width, mat->height, levels, tuple, DebugName(true, true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Surface::~Surface() = default;
|
||||||
|
|
||||||
|
GLuint Surface::Handle(u32 index) const noexcept {
|
||||||
|
if (!textures[index].handle) {
|
||||||
|
return textures[0].handle;
|
||||||
|
}
|
||||||
|
return textures[index].handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
GLuint Surface::CopyHandle() noexcept {
|
||||||
|
if (!copy_texture.handle) {
|
||||||
|
copy_texture = MakeHandle(GL_TEXTURE_2D, GetScaledWidth(), GetScaledHeight(), levels, tuple,
|
||||||
|
DebugName(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (u32 level = 0; level < levels; level++) {
|
||||||
|
const u32 width = GetScaledWidth() >> level;
|
||||||
|
const u32 height = GetScaledHeight() >> level;
|
||||||
|
glCopyImageSubData(Handle(1), GL_TEXTURE_2D, level, 0, 0, 0, copy_texture.handle,
|
||||||
|
GL_TEXTURE_2D, level, 0, 0, 0, width, height, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return copy_texture.handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Surface::Upload(const VideoCore::BufferTextureCopy& upload,
|
void Surface::Upload(const VideoCore::BufferTextureCopy& upload,
|
||||||
const VideoCore::StagingData& staging) {
|
const VideoCore::StagingData& staging) {
|
||||||
ASSERT(stride * GetFormatBytesPerPixel(pixel_format) % 4 == 0);
|
ASSERT(stride * GetFormatBytesPerPixel(pixel_format) % 4 == 0);
|
||||||
|
|
||||||
const GLuint old_tex = OpenGLState::GetCurState().texture_units[0].texture_2d;
|
|
||||||
const u32 unscaled_width = upload.texture_rect.GetWidth();
|
const u32 unscaled_width = upload.texture_rect.GetWidth();
|
||||||
const u32 unscaled_height = upload.texture_rect.GetHeight();
|
const u32 unscaled_height = upload.texture_rect.GetHeight();
|
||||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, unscaled_width);
|
glPixelStorei(GL_UNPACK_ROW_LENGTH, unscaled_width);
|
||||||
|
|
||||||
glActiveTexture(GL_TEXTURE0);
|
glActiveTexture(TEMP_UNIT);
|
||||||
glBindTexture(GL_TEXTURE_2D, Handle(0));
|
glBindTexture(GL_TEXTURE_2D, Handle(0));
|
||||||
|
|
||||||
const auto& tuple = alloc.tuple;
|
|
||||||
glTexSubImage2D(GL_TEXTURE_2D, upload.texture_level, upload.texture_rect.left,
|
glTexSubImage2D(GL_TEXTURE_2D, upload.texture_level, upload.texture_rect.left,
|
||||||
upload.texture_rect.bottom, unscaled_width, unscaled_height, tuple.format,
|
upload.texture_rect.bottom, unscaled_width, unscaled_height, tuple.format,
|
||||||
tuple.type, staging.mapped.data());
|
tuple.type, staging.mapped.data());
|
||||||
|
|
||||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||||
glBindTexture(GL_TEXTURE_2D, old_tex);
|
|
||||||
|
|
||||||
const VideoCore::TextureBlit blit = {
|
const VideoCore::TextureBlit blit = {
|
||||||
.src_level = upload.texture_level,
|
.src_level = upload.texture_level,
|
||||||
|
@ -400,14 +380,12 @@ void Surface::Upload(const VideoCore::BufferTextureCopy& upload,
|
||||||
}
|
}
|
||||||
|
|
||||||
void Surface::UploadCustom(const VideoCore::Material* material, u32 level) {
|
void Surface::UploadCustom(const VideoCore::Material* material, u32 level) {
|
||||||
const GLuint old_tex = OpenGLState::GetCurState().texture_units[0].texture_2d;
|
|
||||||
const auto& tuple = alloc.tuple;
|
|
||||||
const u32 width = material->width;
|
const u32 width = material->width;
|
||||||
const u32 height = material->height;
|
const u32 height = material->height;
|
||||||
const auto color = material->textures[0];
|
const auto color = material->textures[0];
|
||||||
const Common::Rectangle filter_rect{0U, height, width, 0U};
|
const Common::Rectangle filter_rect{0U, height, width, 0U};
|
||||||
|
|
||||||
glActiveTexture(GL_TEXTURE0);
|
glActiveTexture(TEMP_UNIT);
|
||||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, width);
|
glPixelStorei(GL_UNPACK_ROW_LENGTH, width);
|
||||||
|
|
||||||
const auto upload = [&](u32 index, VideoCore::CustomTexture* texture) {
|
const auto upload = [&](u32 index, VideoCore::CustomTexture* texture) {
|
||||||
|
@ -440,7 +418,6 @@ void Surface::UploadCustom(const VideoCore::Material* material, u32 level) {
|
||||||
}
|
}
|
||||||
|
|
||||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||||
glBindTexture(GL_TEXTURE_2D, old_tex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Surface::Download(const VideoCore::BufferTextureCopy& download,
|
void Surface::Download(const VideoCore::BufferTextureCopy& download,
|
||||||
|
@ -491,6 +468,7 @@ bool Surface::DownloadWithoutFbo(const VideoCore::BufferTextureCopy& download,
|
||||||
const auto& tuple = runtime->GetFormatTuple(pixel_format);
|
const auto& tuple = runtime->GetFormatTuple(pixel_format);
|
||||||
const u32 unscaled_width = download.texture_rect.GetWidth();
|
const u32 unscaled_width = download.texture_rect.GetWidth();
|
||||||
|
|
||||||
|
glActiveTexture(TEMP_UNIT);
|
||||||
glPixelStorei(GL_PACK_ROW_LENGTH, unscaled_width);
|
glPixelStorei(GL_PACK_ROW_LENGTH, unscaled_width);
|
||||||
SCOPE_EXIT({ glPixelStorei(GL_PACK_ROW_LENGTH, 0); });
|
SCOPE_EXIT({ glPixelStorei(GL_PACK_ROW_LENGTH, 0); });
|
||||||
|
|
||||||
|
@ -541,27 +519,24 @@ void Surface::Attach(GLenum target, u32 level, u32 layer, bool scaled) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Surface::Swap(const VideoCore::Material* mat) {
|
void Surface::ScaleUp(u32 new_scale) {
|
||||||
const VideoCore::CustomPixelFormat format{mat->format};
|
if (res_scale == new_scale || new_scale == 1) {
|
||||||
if (!driver->IsCustomFormatSupported(format)) {
|
return;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
runtime->Recycle(MakeTag(), std::move(alloc));
|
|
||||||
|
|
||||||
SurfaceParams params = *this;
|
res_scale = new_scale;
|
||||||
params.width = mat->width;
|
textures[1] = MakeHandle(GL_TEXTURE_2D, GetScaledWidth(), GetScaledHeight(), levels, tuple,
|
||||||
params.height = mat->height;
|
DebugName(true));
|
||||||
params.custom_format = mat->format;
|
|
||||||
alloc = runtime->Allocate(params, mat);
|
|
||||||
|
|
||||||
LOG_DEBUG(Render_OpenGL, "Swapped {}x{} {} surface at address {:#x} to {}x{} {}",
|
VideoCore::TextureBlit blit = {
|
||||||
GetScaledWidth(), GetScaledHeight(), VideoCore::PixelFormatAsString(pixel_format),
|
.src_rect = GetRect(),
|
||||||
addr, width, height, VideoCore::CustomPixelFormatAsString(format));
|
.dst_rect = GetScaledRect(),
|
||||||
|
};
|
||||||
custom_format = format;
|
for (u32 level = 0; level < levels; level++) {
|
||||||
material = mat;
|
blit.src_level = level;
|
||||||
|
blit.dst_level = level;
|
||||||
return true;
|
BlitScale(blit, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 Surface::GetInternalBytesPerPixel() const {
|
u32 Surface::GetInternalBytesPerPixel() const {
|
||||||
|
@ -591,27 +566,11 @@ void Surface::BlitScale(const VideoCore::TextureBlit& blit, bool up_scale) {
|
||||||
blit.dst_rect.right, blit.dst_rect.top, buffer_mask, filter);
|
blit.dst_rect.right, blit.dst_rect.top, buffer_mask, filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
HostTextureTag Surface::MakeTag() const noexcept {
|
Framebuffer::Framebuffer(TextureRuntime& runtime, const VideoCore::FramebufferParams& params,
|
||||||
return HostTextureTag{
|
const Surface* color, const Surface* depth)
|
||||||
.width = alloc.width,
|
: VideoCore::FramebufferParams{params}, res_scale{color ? color->res_scale
|
||||||
.height = alloc.height,
|
: (depth ? depth->res_scale : 1u)} {
|
||||||
.levels = alloc.levels,
|
|
||||||
.res_scale = alloc.res_scale,
|
|
||||||
.tuple = alloc.tuple,
|
|
||||||
.type = texture_type,
|
|
||||||
.is_custom = alloc.is_custom,
|
|
||||||
.has_normal = HasNormalMap(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
Framebuffer::Framebuffer(TextureRuntime& runtime, const Surface* color, u32 color_level,
|
|
||||||
const Surface* depth_stencil, u32 depth_level, const Pica::Regs& regs,
|
|
||||||
Common::Rectangle<u32> surfaces_rect)
|
|
||||||
: VideoCore::FramebufferBase{regs, color, color_level,
|
|
||||||
depth_stencil, depth_level, surfaces_rect} {
|
|
||||||
|
|
||||||
const bool shadow_rendering = regs.framebuffer.IsShadowRendering();
|
|
||||||
const bool has_stencil = regs.framebuffer.HasStencil();
|
|
||||||
if (shadow_rendering && !color) {
|
if (shadow_rendering && !color) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -619,33 +578,15 @@ Framebuffer::Framebuffer(TextureRuntime& runtime, const Surface* color, u32 colo
|
||||||
if (color) {
|
if (color) {
|
||||||
attachments[0] = color->Handle();
|
attachments[0] = color->Handle();
|
||||||
}
|
}
|
||||||
if (depth_stencil) {
|
if (depth) {
|
||||||
attachments[1] = depth_stencil->Handle();
|
attachments[1] = depth->Handle();
|
||||||
}
|
}
|
||||||
|
|
||||||
const FramebufferInfo info = {
|
|
||||||
.color = attachments[0],
|
|
||||||
.depth = attachments[1],
|
|
||||||
.color_level = color_level,
|
|
||||||
.depth_level = depth_level,
|
|
||||||
};
|
|
||||||
|
|
||||||
const u64 hash = Common::ComputeHash64(&info, sizeof(FramebufferInfo));
|
|
||||||
auto [it, new_framebuffer] = runtime.framebuffer_cache.try_emplace(hash);
|
|
||||||
|
|
||||||
if (!new_framebuffer) {
|
|
||||||
handle = it->second.handle;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const GLuint old_fbo = OpenGLState::GetCurState().draw.draw_framebuffer;
|
|
||||||
|
|
||||||
OGLFramebuffer& framebuffer = it->second;
|
|
||||||
framebuffer.Create();
|
framebuffer.Create();
|
||||||
handle = it->second.handle;
|
|
||||||
|
|
||||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer.handle);
|
OpenGLState state = OpenGLState::GetCurState();
|
||||||
SCOPE_EXIT({ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, old_fbo); });
|
state.draw.draw_framebuffer = framebuffer.handle;
|
||||||
|
state.Apply();
|
||||||
|
|
||||||
if (shadow_rendering) {
|
if (shadow_rendering) {
|
||||||
glFramebufferParameteri(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_WIDTH,
|
glFramebufferParameteri(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_WIDTH,
|
||||||
|
@ -658,13 +599,13 @@ Framebuffer::Framebuffer(TextureRuntime& runtime, const Surface* color, u32 colo
|
||||||
} else {
|
} else {
|
||||||
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
|
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
|
||||||
color ? color->Handle() : 0, color_level);
|
color ? color->Handle() : 0, color_level);
|
||||||
if (depth_stencil) {
|
if (depth) {
|
||||||
if (has_stencil) {
|
if (depth->pixel_format == PixelFormat::D24S8) {
|
||||||
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
|
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
|
||||||
GL_TEXTURE_2D, depth_stencil->Handle(), depth_level);
|
GL_TEXTURE_2D, depth->Handle(), depth_level);
|
||||||
} else {
|
} else {
|
||||||
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D,
|
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D,
|
||||||
depth_stencil->Handle(), depth_level);
|
depth->Handle(), depth_level);
|
||||||
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0,
|
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0,
|
||||||
0);
|
0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,46 +27,6 @@ struct FormatTuple {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct HostTextureTag {
|
|
||||||
u32 width;
|
|
||||||
u32 height;
|
|
||||||
u32 levels;
|
|
||||||
u32 res_scale;
|
|
||||||
FormatTuple tuple;
|
|
||||||
VideoCore::TextureType type;
|
|
||||||
bool is_custom;
|
|
||||||
bool has_normal;
|
|
||||||
|
|
||||||
bool operator==(const HostTextureTag& other) const noexcept {
|
|
||||||
return std::tie(tuple, type, width, height, levels, res_scale, is_custom, has_normal) ==
|
|
||||||
std::tie(other.tuple, other.type, other.width, other.height, other.levels,
|
|
||||||
other.res_scale, other.is_custom, other.has_normal);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Hash {
|
|
||||||
const u64 operator()(const HostTextureTag& tag) const {
|
|
||||||
return Common::ComputeHash64(&tag, sizeof(HostTextureTag));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
static_assert(std::has_unique_object_representations_v<HostTextureTag>,
|
|
||||||
"HostTextureTag is not suitable for hashing!");
|
|
||||||
|
|
||||||
struct Allocation {
|
|
||||||
std::array<OGLTexture, 3> textures;
|
|
||||||
std::array<GLuint, 3> handles;
|
|
||||||
FormatTuple tuple;
|
|
||||||
u32 width;
|
|
||||||
u32 height;
|
|
||||||
u32 levels;
|
|
||||||
u32 res_scale;
|
|
||||||
bool is_custom;
|
|
||||||
|
|
||||||
operator bool() const noexcept {
|
|
||||||
return textures[0].handle;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class Surface;
|
class Surface;
|
||||||
class Driver;
|
class Driver;
|
||||||
|
|
||||||
|
@ -82,8 +42,8 @@ public:
|
||||||
explicit TextureRuntime(const Driver& driver, VideoCore::RendererBase& renderer);
|
explicit TextureRuntime(const Driver& driver, VideoCore::RendererBase& renderer);
|
||||||
~TextureRuntime();
|
~TextureRuntime();
|
||||||
|
|
||||||
/// Clears all cached runtime resources
|
/// Returns the removal threshold ticks for the garbage collector
|
||||||
void Reset();
|
u32 RemoveThreshold();
|
||||||
|
|
||||||
/// Returns true if the provided pixel format cannot be used natively by the runtime.
|
/// Returns true if the provided pixel format cannot be used natively by the runtime.
|
||||||
bool NeedsConversion(VideoCore::PixelFormat pixel_format) const;
|
bool NeedsConversion(VideoCore::PixelFormat pixel_format) const;
|
||||||
|
@ -111,13 +71,6 @@ public:
|
||||||
void GenerateMipmaps(Surface& surface);
|
void GenerateMipmaps(Surface& surface);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// Takes back ownership of the allocation for recycling
|
|
||||||
void Recycle(const HostTextureTag tag, Allocation&& alloc);
|
|
||||||
|
|
||||||
/// Allocates a texture with the specified dimentions and format
|
|
||||||
Allocation Allocate(const VideoCore::SurfaceParams& params,
|
|
||||||
const VideoCore::Material* material = nullptr);
|
|
||||||
|
|
||||||
/// Returns the OpenGL driver class
|
/// Returns the OpenGL driver class
|
||||||
const Driver& GetDriver() const {
|
const Driver& GetDriver() const {
|
||||||
return driver;
|
return driver;
|
||||||
|
@ -127,8 +80,6 @@ private:
|
||||||
const Driver& driver;
|
const Driver& driver;
|
||||||
BlitHelper blit_helper;
|
BlitHelper blit_helper;
|
||||||
std::vector<u8> staging_buffer;
|
std::vector<u8> staging_buffer;
|
||||||
std::unordered_multimap<HostTextureTag, Allocation, HostTextureTag::Hash> alloc_cache;
|
|
||||||
std::unordered_map<u64, OGLFramebuffer, Common::IdentityHash<u64>> framebuffer_cache;
|
|
||||||
std::array<OGLFramebuffer, 3> draw_fbos;
|
std::array<OGLFramebuffer, 3> draw_fbos;
|
||||||
std::array<OGLFramebuffer, 3> read_fbos;
|
std::array<OGLFramebuffer, 3> read_fbos;
|
||||||
};
|
};
|
||||||
|
@ -136,6 +87,8 @@ private:
|
||||||
class Surface : public VideoCore::SurfaceBase {
|
class Surface : public VideoCore::SurfaceBase {
|
||||||
public:
|
public:
|
||||||
explicit Surface(TextureRuntime& runtime, const VideoCore::SurfaceParams& params);
|
explicit Surface(TextureRuntime& runtime, const VideoCore::SurfaceParams& params);
|
||||||
|
explicit Surface(TextureRuntime& runtime, const VideoCore::SurfaceBase& surface,
|
||||||
|
const VideoCore::Material* material);
|
||||||
~Surface();
|
~Surface();
|
||||||
|
|
||||||
Surface(const Surface&) = delete;
|
Surface(const Surface&) = delete;
|
||||||
|
@ -144,13 +97,15 @@ public:
|
||||||
Surface(Surface&& o) noexcept = default;
|
Surface(Surface&& o) noexcept = default;
|
||||||
Surface& operator=(Surface&& o) noexcept = default;
|
Surface& operator=(Surface&& o) noexcept = default;
|
||||||
|
|
||||||
[[nodiscard]] GLuint Handle(u32 index = 1) const noexcept {
|
[[nodiscard]] const FormatTuple& Tuple() const noexcept {
|
||||||
return alloc.handles[index];
|
return tuple;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] const FormatTuple& Tuple() const noexcept {
|
/// Returns the texture handle at index, otherwise the first one if not valid.
|
||||||
return alloc.tuple;
|
GLuint Handle(u32 index = 1) const noexcept;
|
||||||
}
|
|
||||||
|
/// Returns a copy of the upscaled texture handle, used for feedback loops.
|
||||||
|
GLuint CopyHandle() noexcept;
|
||||||
|
|
||||||
/// Uploads pixel data in staging to a rectangle region of the surface texture
|
/// Uploads pixel data in staging to a rectangle region of the surface texture
|
||||||
void Upload(const VideoCore::BufferTextureCopy& upload, const VideoCore::StagingData& staging);
|
void Upload(const VideoCore::BufferTextureCopy& upload, const VideoCore::StagingData& staging);
|
||||||
|
@ -165,8 +120,8 @@ public:
|
||||||
/// Attaches a handle of surface to the specified framebuffer target
|
/// Attaches a handle of surface to the specified framebuffer target
|
||||||
void Attach(GLenum target, u32 level, u32 layer, bool scaled = true);
|
void Attach(GLenum target, u32 level, u32 layer, bool scaled = true);
|
||||||
|
|
||||||
/// Swaps the internal allocation to match the provided material
|
/// Scales up the surface to match the new resolution scale.
|
||||||
bool Swap(const VideoCore::Material* material);
|
void ScaleUp(u32 new_scale);
|
||||||
|
|
||||||
/// Returns the bpp of the internal surface format
|
/// Returns the bpp of the internal surface format
|
||||||
u32 GetInternalBytesPerPixel() const;
|
u32 GetInternalBytesPerPixel() const;
|
||||||
|
@ -179,24 +134,32 @@ private:
|
||||||
bool DownloadWithoutFbo(const VideoCore::BufferTextureCopy& download,
|
bool DownloadWithoutFbo(const VideoCore::BufferTextureCopy& download,
|
||||||
const VideoCore::StagingData& staging);
|
const VideoCore::StagingData& staging);
|
||||||
|
|
||||||
/// Returns the texture tag of the current allocation
|
|
||||||
HostTextureTag MakeTag() const noexcept;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const Driver* driver;
|
const Driver* driver;
|
||||||
TextureRuntime* runtime;
|
TextureRuntime* runtime;
|
||||||
Allocation alloc{};
|
std::array<OGLTexture, 3> textures;
|
||||||
|
OGLTexture copy_texture;
|
||||||
|
FormatTuple tuple;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Framebuffer : public VideoCore::FramebufferBase {
|
class Framebuffer : public VideoCore::FramebufferParams {
|
||||||
public:
|
public:
|
||||||
explicit Framebuffer(TextureRuntime& runtime, const Surface* color, u32 color_level,
|
explicit Framebuffer(TextureRuntime& runtime, const VideoCore::FramebufferParams& params,
|
||||||
const Surface* depth_stencil, u32 depth_level, const Pica::Regs& regs,
|
const Surface* color, const Surface* depth_stencil);
|
||||||
Common::Rectangle<u32> surfaces_rect);
|
|
||||||
~Framebuffer();
|
~Framebuffer();
|
||||||
|
|
||||||
|
Framebuffer(const Framebuffer&) = delete;
|
||||||
|
Framebuffer& operator=(const Framebuffer&) = delete;
|
||||||
|
|
||||||
|
Framebuffer(Framebuffer&& o) noexcept = default;
|
||||||
|
Framebuffer& operator=(Framebuffer&& o) noexcept = default;
|
||||||
|
|
||||||
|
[[nodiscard]] u32 Scale() const noexcept {
|
||||||
|
return res_scale;
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] GLuint Handle() const noexcept {
|
[[nodiscard]] GLuint Handle() const noexcept {
|
||||||
return handle;
|
return framebuffer.handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] GLuint Attachment(VideoCore::SurfaceType type) const noexcept {
|
[[nodiscard]] GLuint Attachment(VideoCore::SurfaceType type) const noexcept {
|
||||||
|
@ -208,8 +171,9 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
u32 res_scale{1};
|
||||||
std::array<GLuint, 2> attachments{};
|
std::array<GLuint, 2> attachments{};
|
||||||
GLuint handle{};
|
OGLFramebuffer framebuffer;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Sampler {
|
class Sampler {
|
||||||
|
|
|
@ -2,20 +2,18 @@
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#include <queue>
|
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "common/microprofile.h"
|
#include "common/microprofile.h"
|
||||||
#include "common/settings.h"
|
#include "common/settings.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/dumping/backend.h"
|
|
||||||
#include "core/frontend/emu_window.h"
|
#include "core/frontend/emu_window.h"
|
||||||
#include "core/frontend/framebuffer_layout.h"
|
#include "core/frontend/framebuffer_layout.h"
|
||||||
#include "core/hw/hw.h"
|
#include "core/hw/hw.h"
|
||||||
#include "core/hw/lcd.h"
|
#include "core/hw/lcd.h"
|
||||||
#include "core/memory.h"
|
#include "core/memory.h"
|
||||||
#include "video_core/rasterizer_interface.h"
|
|
||||||
#include "video_core/renderer_opengl/gl_shader_util.h"
|
#include "video_core/renderer_opengl/gl_shader_util.h"
|
||||||
#include "video_core/renderer_opengl/gl_state.h"
|
#include "video_core/renderer_opengl/gl_state.h"
|
||||||
|
#include "video_core/renderer_opengl/gl_texture_mailbox.h"
|
||||||
#include "video_core/renderer_opengl/gl_vars.h"
|
#include "video_core/renderer_opengl/gl_vars.h"
|
||||||
#include "video_core/renderer_opengl/post_processing_opengl.h"
|
#include "video_core/renderer_opengl/post_processing_opengl.h"
|
||||||
#include "video_core/renderer_opengl/renderer_opengl.h"
|
#include "video_core/renderer_opengl/renderer_opengl.h"
|
||||||
|
@ -31,232 +29,6 @@ namespace OpenGL {
|
||||||
MICROPROFILE_DEFINE(OpenGL_RenderFrame, "OpenGL", "Render Frame", MP_RGB(128, 128, 64));
|
MICROPROFILE_DEFINE(OpenGL_RenderFrame, "OpenGL", "Render Frame", MP_RGB(128, 128, 64));
|
||||||
MICROPROFILE_DEFINE(OpenGL_WaitPresent, "OpenGL", "Wait For Present", MP_RGB(128, 128, 128));
|
MICROPROFILE_DEFINE(OpenGL_WaitPresent, "OpenGL", "Wait For Present", MP_RGB(128, 128, 128));
|
||||||
|
|
||||||
// If the size of this is too small, it ends up creating a soft cap on FPS as the renderer will have
|
|
||||||
// to wait on available presentation frames. There doesn't seem to be much of a downside to a larger
|
|
||||||
// number but 9 swap textures at 60FPS presentation allows for 800% speed so thats probably fine
|
|
||||||
#ifdef ANDROID
|
|
||||||
// Reduce the size of swap_chain, since the UI only allows upto 200% speed.
|
|
||||||
constexpr std::size_t SWAP_CHAIN_SIZE = 6;
|
|
||||||
#else
|
|
||||||
constexpr std::size_t SWAP_CHAIN_SIZE = 9;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
class OGLTextureMailboxException : public std::runtime_error {
|
|
||||||
public:
|
|
||||||
using std::runtime_error::runtime_error;
|
|
||||||
};
|
|
||||||
|
|
||||||
class OGLTextureMailbox : public Frontend::TextureMailbox {
|
|
||||||
public:
|
|
||||||
std::mutex swap_chain_lock;
|
|
||||||
std::condition_variable free_cv;
|
|
||||||
std::condition_variable present_cv;
|
|
||||||
std::array<Frontend::Frame, SWAP_CHAIN_SIZE> swap_chain{};
|
|
||||||
std::queue<Frontend::Frame*> free_queue{};
|
|
||||||
std::deque<Frontend::Frame*> present_queue{};
|
|
||||||
Frontend::Frame* previous_frame = nullptr;
|
|
||||||
|
|
||||||
OGLTextureMailbox(bool has_debug_tool_ = false) : has_debug_tool{has_debug_tool_} {
|
|
||||||
for (auto& frame : swap_chain) {
|
|
||||||
free_queue.push(&frame);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
~OGLTextureMailbox() override {
|
|
||||||
// lock the mutex and clear out the present and free_queues and notify any people who are
|
|
||||||
// blocked to prevent deadlock on shutdown
|
|
||||||
std::scoped_lock lock(swap_chain_lock);
|
|
||||||
std::queue<Frontend::Frame*>().swap(free_queue);
|
|
||||||
present_queue.clear();
|
|
||||||
present_cv.notify_all();
|
|
||||||
free_cv.notify_all();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ReloadPresentFrame(Frontend::Frame* frame, u32 height, u32 width) override {
|
|
||||||
frame->present.Release();
|
|
||||||
frame->present.Create();
|
|
||||||
GLint previous_draw_fbo{};
|
|
||||||
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &previous_draw_fbo);
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, frame->present.handle);
|
|
||||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
|
|
||||||
frame->color.handle);
|
|
||||||
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
|
|
||||||
LOG_CRITICAL(Render_OpenGL, "Failed to recreate present FBO!");
|
|
||||||
}
|
|
||||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, previous_draw_fbo);
|
|
||||||
frame->color_reloaded = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ReloadRenderFrame(Frontend::Frame* frame, u32 width, u32 height) override {
|
|
||||||
OpenGLState prev_state = OpenGLState::GetCurState();
|
|
||||||
OpenGLState state = OpenGLState::GetCurState();
|
|
||||||
|
|
||||||
// Recreate the color texture attachment
|
|
||||||
frame->color.Release();
|
|
||||||
frame->color.Create();
|
|
||||||
state.renderbuffer = frame->color.handle;
|
|
||||||
state.Apply();
|
|
||||||
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, width, height);
|
|
||||||
|
|
||||||
// Recreate the FBO for the render target
|
|
||||||
frame->render.Release();
|
|
||||||
frame->render.Create();
|
|
||||||
state.draw.read_framebuffer = frame->render.handle;
|
|
||||||
state.draw.draw_framebuffer = frame->render.handle;
|
|
||||||
state.Apply();
|
|
||||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
|
|
||||||
frame->color.handle);
|
|
||||||
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
|
|
||||||
LOG_CRITICAL(Render_OpenGL, "Failed to recreate render FBO!");
|
|
||||||
}
|
|
||||||
prev_state.Apply();
|
|
||||||
frame->width = width;
|
|
||||||
frame->height = height;
|
|
||||||
frame->color_reloaded = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Frontend::Frame* GetRenderFrame() override {
|
|
||||||
std::unique_lock<std::mutex> lock(swap_chain_lock);
|
|
||||||
|
|
||||||
// If theres no free frames, we will reuse the oldest render frame
|
|
||||||
if (free_queue.empty()) {
|
|
||||||
auto frame = present_queue.back();
|
|
||||||
present_queue.pop_back();
|
|
||||||
return frame;
|
|
||||||
}
|
|
||||||
|
|
||||||
Frontend::Frame* frame = free_queue.front();
|
|
||||||
free_queue.pop();
|
|
||||||
return frame;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ReleaseRenderFrame(Frontend::Frame* frame) override {
|
|
||||||
std::unique_lock<std::mutex> lock(swap_chain_lock);
|
|
||||||
present_queue.push_front(frame);
|
|
||||||
present_cv.notify_one();
|
|
||||||
|
|
||||||
DebugNotifyNextFrame();
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is virtual as it is to be overriden in OGLVideoDumpingMailbox below.
|
|
||||||
virtual void LoadPresentFrame() {
|
|
||||||
// free the previous frame and add it back to the free queue
|
|
||||||
if (previous_frame) {
|
|
||||||
free_queue.push(previous_frame);
|
|
||||||
free_cv.notify_one();
|
|
||||||
}
|
|
||||||
|
|
||||||
// the newest entries are pushed to the front of the queue
|
|
||||||
Frontend::Frame* frame = present_queue.front();
|
|
||||||
present_queue.pop_front();
|
|
||||||
// remove all old entries from the present queue and move them back to the free_queue
|
|
||||||
for (auto f : present_queue) {
|
|
||||||
free_queue.push(f);
|
|
||||||
}
|
|
||||||
present_queue.clear();
|
|
||||||
previous_frame = frame;
|
|
||||||
}
|
|
||||||
|
|
||||||
Frontend::Frame* TryGetPresentFrame(int timeout_ms) override {
|
|
||||||
DebugWaitForNextFrame();
|
|
||||||
|
|
||||||
std::unique_lock<std::mutex> lock(swap_chain_lock);
|
|
||||||
// wait for new entries in the present_queue
|
|
||||||
present_cv.wait_for(lock, std::chrono::milliseconds(timeout_ms),
|
|
||||||
[&] { return !present_queue.empty(); });
|
|
||||||
if (present_queue.empty()) {
|
|
||||||
// timed out waiting for a frame to draw so return the previous frame
|
|
||||||
return previous_frame;
|
|
||||||
}
|
|
||||||
|
|
||||||
LoadPresentFrame();
|
|
||||||
return previous_frame;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::mutex debug_synch_mutex;
|
|
||||||
std::condition_variable debug_synch_condition;
|
|
||||||
std::atomic_int frame_for_debug{};
|
|
||||||
const bool has_debug_tool; // When true, using a GPU debugger, so keep frames in lock-step
|
|
||||||
|
|
||||||
/// Signal that a new frame is available (called from GPU thread)
|
|
||||||
void DebugNotifyNextFrame() {
|
|
||||||
if (!has_debug_tool) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
frame_for_debug++;
|
|
||||||
std::lock_guard lock{debug_synch_mutex};
|
|
||||||
debug_synch_condition.notify_one();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wait for a new frame to be available (called from presentation thread)
|
|
||||||
void DebugWaitForNextFrame() {
|
|
||||||
if (!has_debug_tool) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const int last_frame = frame_for_debug;
|
|
||||||
std::unique_lock lock{debug_synch_mutex};
|
|
||||||
debug_synch_condition.wait(lock,
|
|
||||||
[this, last_frame] { return frame_for_debug > last_frame; });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/// This mailbox is different in that it will never discard rendered frames
|
|
||||||
class OGLVideoDumpingMailbox : public OGLTextureMailbox {
|
|
||||||
public:
|
|
||||||
bool quit = false;
|
|
||||||
|
|
||||||
Frontend::Frame* GetRenderFrame() override {
|
|
||||||
std::unique_lock<std::mutex> lock(swap_chain_lock);
|
|
||||||
|
|
||||||
// If theres no free frames, we will wait until one shows up
|
|
||||||
if (free_queue.empty()) {
|
|
||||||
free_cv.wait(lock, [&] { return (!free_queue.empty() || quit); });
|
|
||||||
if (quit) {
|
|
||||||
throw OGLTextureMailboxException("VideoDumpingMailbox quitting");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (free_queue.empty()) {
|
|
||||||
LOG_CRITICAL(Render_OpenGL, "Could not get free frame");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Frontend::Frame* frame = free_queue.front();
|
|
||||||
free_queue.pop();
|
|
||||||
return frame;
|
|
||||||
}
|
|
||||||
|
|
||||||
void LoadPresentFrame() override {
|
|
||||||
// free the previous frame and add it back to the free queue
|
|
||||||
if (previous_frame) {
|
|
||||||
free_queue.push(previous_frame);
|
|
||||||
free_cv.notify_one();
|
|
||||||
}
|
|
||||||
|
|
||||||
Frontend::Frame* frame = present_queue.back();
|
|
||||||
present_queue.pop_back();
|
|
||||||
previous_frame = frame;
|
|
||||||
|
|
||||||
// Do not remove entries from the present_queue, as video dumping would require
|
|
||||||
// that we preserve all frames
|
|
||||||
}
|
|
||||||
|
|
||||||
Frontend::Frame* TryGetPresentFrame(int timeout_ms) override {
|
|
||||||
std::unique_lock<std::mutex> lock(swap_chain_lock);
|
|
||||||
// wait for new entries in the present_queue
|
|
||||||
present_cv.wait_for(lock, std::chrono::milliseconds(timeout_ms),
|
|
||||||
[&] { return !present_queue.empty(); });
|
|
||||||
if (present_queue.empty()) {
|
|
||||||
// timed out waiting for a frame
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
LoadPresentFrame();
|
|
||||||
return previous_frame;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Vertex structure that the drawn screen rectangles are composed of.
|
* Vertex structure that the drawn screen rectangles are composed of.
|
||||||
*/
|
*/
|
||||||
|
@ -559,8 +331,15 @@ void RendererOpenGL::InitOpenGLObjects() {
|
||||||
glClearColor(Settings::values.bg_red.GetValue(), Settings::values.bg_green.GetValue(),
|
glClearColor(Settings::values.bg_red.GetValue(), Settings::values.bg_green.GetValue(),
|
||||||
Settings::values.bg_blue.GetValue(), 0.0f);
|
Settings::values.bg_blue.GetValue(), 0.0f);
|
||||||
|
|
||||||
filter_sampler.Create();
|
for (size_t i = 0; i < samplers.size(); i++) {
|
||||||
ReloadSampler();
|
samplers[i].Create();
|
||||||
|
glSamplerParameteri(samplers[i].handle, GL_TEXTURE_MIN_FILTER,
|
||||||
|
i == 0 ? GL_NEAREST : GL_LINEAR);
|
||||||
|
glSamplerParameteri(samplers[i].handle, GL_TEXTURE_MAG_FILTER,
|
||||||
|
i == 0 ? GL_NEAREST : GL_LINEAR);
|
||||||
|
glSamplerParameteri(samplers[i].handle, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||||
|
glSamplerParameteri(samplers[i].handle, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||||
|
}
|
||||||
|
|
||||||
ReloadShader();
|
ReloadShader();
|
||||||
|
|
||||||
|
@ -608,15 +387,6 @@ void RendererOpenGL::InitOpenGLObjects() {
|
||||||
state.Apply();
|
state.Apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
void RendererOpenGL::ReloadSampler() {
|
|
||||||
glSamplerParameteri(filter_sampler.handle, GL_TEXTURE_MIN_FILTER,
|
|
||||||
Settings::values.filter_mode ? GL_LINEAR : GL_NEAREST);
|
|
||||||
glSamplerParameteri(filter_sampler.handle, GL_TEXTURE_MAG_FILTER,
|
|
||||||
Settings::values.filter_mode ? GL_LINEAR : GL_NEAREST);
|
|
||||||
glSamplerParameteri(filter_sampler.handle, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
||||||
glSamplerParameteri(filter_sampler.handle, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RendererOpenGL::ReloadShader() {
|
void RendererOpenGL::ReloadShader() {
|
||||||
// Link shaders and get variable locations
|
// Link shaders and get variable locations
|
||||||
std::string shader_data;
|
std::string shader_data;
|
||||||
|
@ -793,13 +563,14 @@ void RendererOpenGL::DrawSingleScreen(const ScreenInfo& screen_info, float x, fl
|
||||||
}
|
}
|
||||||
|
|
||||||
const u32 scale_factor = GetResolutionScaleFactor();
|
const u32 scale_factor = GetResolutionScaleFactor();
|
||||||
|
const GLuint sampler = samplers[Settings::values.filter_mode.GetValue()].handle;
|
||||||
glUniform4f(uniform_i_resolution, static_cast<float>(screen_info.texture.width * scale_factor),
|
glUniform4f(uniform_i_resolution, static_cast<float>(screen_info.texture.width * scale_factor),
|
||||||
static_cast<float>(screen_info.texture.height * scale_factor),
|
static_cast<float>(screen_info.texture.height * scale_factor),
|
||||||
1.0f / static_cast<float>(screen_info.texture.width * scale_factor),
|
1.0f / static_cast<float>(screen_info.texture.width * scale_factor),
|
||||||
1.0f / static_cast<float>(screen_info.texture.height * scale_factor));
|
1.0f / static_cast<float>(screen_info.texture.height * scale_factor));
|
||||||
glUniform4f(uniform_o_resolution, h, w, 1.0f / h, 1.0f / w);
|
glUniform4f(uniform_o_resolution, h, w, 1.0f / h, 1.0f / w);
|
||||||
state.texture_units[0].texture_2d = screen_info.display_texture;
|
state.texture_units[0].texture_2d = screen_info.display_texture;
|
||||||
state.texture_units[0].sampler = filter_sampler.handle;
|
state.texture_units[0].sampler = sampler;
|
||||||
state.Apply();
|
state.Apply();
|
||||||
|
|
||||||
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices.data());
|
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices.data());
|
||||||
|
@ -862,6 +633,7 @@ void RendererOpenGL::DrawSingleScreenStereo(const ScreenInfo& screen_info_l,
|
||||||
}
|
}
|
||||||
|
|
||||||
const u32 scale_factor = GetResolutionScaleFactor();
|
const u32 scale_factor = GetResolutionScaleFactor();
|
||||||
|
const GLuint sampler = samplers[Settings::values.filter_mode.GetValue()].handle;
|
||||||
glUniform4f(uniform_i_resolution,
|
glUniform4f(uniform_i_resolution,
|
||||||
static_cast<float>(screen_info_l.texture.width * scale_factor),
|
static_cast<float>(screen_info_l.texture.width * scale_factor),
|
||||||
static_cast<float>(screen_info_l.texture.height * scale_factor),
|
static_cast<float>(screen_info_l.texture.height * scale_factor),
|
||||||
|
@ -870,8 +642,8 @@ void RendererOpenGL::DrawSingleScreenStereo(const ScreenInfo& screen_info_l,
|
||||||
glUniform4f(uniform_o_resolution, h, w, 1.0f / h, 1.0f / w);
|
glUniform4f(uniform_o_resolution, h, w, 1.0f / h, 1.0f / w);
|
||||||
state.texture_units[0].texture_2d = screen_info_l.display_texture;
|
state.texture_units[0].texture_2d = screen_info_l.display_texture;
|
||||||
state.texture_units[1].texture_2d = screen_info_r.display_texture;
|
state.texture_units[1].texture_2d = screen_info_r.display_texture;
|
||||||
state.texture_units[0].sampler = filter_sampler.handle;
|
state.texture_units[0].sampler = sampler;
|
||||||
state.texture_units[1].sampler = filter_sampler.handle;
|
state.texture_units[1].sampler = sampler;
|
||||||
state.Apply();
|
state.Apply();
|
||||||
|
|
||||||
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices.data());
|
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices.data());
|
||||||
|
@ -894,11 +666,6 @@ void RendererOpenGL::DrawScreens(const Layout::FramebufferLayout& layout, bool f
|
||||||
Settings::values.bg_blue.GetValue(), 0.0f);
|
Settings::values.bg_blue.GetValue(), 0.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settings.sampler_update_requested.exchange(false)) {
|
|
||||||
// Set the new filtering mode for the sampler
|
|
||||||
ReloadSampler();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (settings.shader_update_requested.exchange(false)) {
|
if (settings.shader_update_requested.exchange(false)) {
|
||||||
// Update fragment shader before drawing
|
// Update fragment shader before drawing
|
||||||
shader.Release();
|
shader.Release();
|
||||||
|
@ -1119,7 +886,7 @@ void RendererOpenGL::TryPresent(int timeout_ms, bool is_secondary) {
|
||||||
void RendererOpenGL::PrepareVideoDumping() {
|
void RendererOpenGL::PrepareVideoDumping() {
|
||||||
auto* mailbox = static_cast<OGLVideoDumpingMailbox*>(frame_dumper.mailbox.get());
|
auto* mailbox = static_cast<OGLVideoDumpingMailbox*>(frame_dumper.mailbox.get());
|
||||||
{
|
{
|
||||||
std::unique_lock lock(mailbox->swap_chain_lock);
|
std::scoped_lock lock{mailbox->swap_chain_lock};
|
||||||
mailbox->quit = false;
|
mailbox->quit = false;
|
||||||
}
|
}
|
||||||
frame_dumper.StartDumping();
|
frame_dumper.StartDumping();
|
||||||
|
@ -1129,7 +896,7 @@ void RendererOpenGL::CleanupVideoDumping() {
|
||||||
frame_dumper.StopDumping();
|
frame_dumper.StopDumping();
|
||||||
auto* mailbox = static_cast<OGLVideoDumpingMailbox*>(frame_dumper.mailbox.get());
|
auto* mailbox = static_cast<OGLVideoDumpingMailbox*>(frame_dumper.mailbox.get());
|
||||||
{
|
{
|
||||||
std::unique_lock lock(mailbox->swap_chain_lock);
|
std::scoped_lock lock{mailbox->swap_chain_lock};
|
||||||
mailbox->quit = true;
|
mailbox->quit = true;
|
||||||
}
|
}
|
||||||
mailbox->free_cv.notify_one();
|
mailbox->free_cv.notify_one();
|
||||||
|
|
|
@ -21,20 +21,6 @@ namespace Core {
|
||||||
class System;
|
class System;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Frontend {
|
|
||||||
|
|
||||||
struct Frame {
|
|
||||||
u32 width{}; /// Width of the frame (to detect resize)
|
|
||||||
u32 height{}; /// Height of the frame
|
|
||||||
bool color_reloaded = false; /// Texture attachment was recreated (ie: resized)
|
|
||||||
OpenGL::OGLRenderbuffer color{}; /// Buffer shared between the render/present FBO
|
|
||||||
OpenGL::OGLFramebuffer render{}; /// FBO created on the render thread
|
|
||||||
OpenGL::OGLFramebuffer present{}; /// FBO created on the present thread
|
|
||||||
GLsync render_fence{}; /// Fence created on the render thread
|
|
||||||
GLsync present_fence{}; /// Fence created on the presentation thread
|
|
||||||
};
|
|
||||||
} // namespace Frontend
|
|
||||||
|
|
||||||
namespace OpenGL {
|
namespace OpenGL {
|
||||||
|
|
||||||
/// Structure used for storing information about the textures for each 3DS screen
|
/// Structure used for storing information about the textures for each 3DS screen
|
||||||
|
@ -72,7 +58,6 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void InitOpenGLObjects();
|
void InitOpenGLObjects();
|
||||||
void ReloadSampler();
|
|
||||||
void ReloadShader();
|
void ReloadShader();
|
||||||
void PrepareRendertarget();
|
void PrepareRendertarget();
|
||||||
void RenderScreenshot();
|
void RenderScreenshot();
|
||||||
|
@ -109,9 +94,9 @@ private:
|
||||||
OGLBuffer vertex_buffer;
|
OGLBuffer vertex_buffer;
|
||||||
OGLProgram shader;
|
OGLProgram shader;
|
||||||
OGLFramebuffer screenshot_framebuffer;
|
OGLFramebuffer screenshot_framebuffer;
|
||||||
OGLSampler filter_sampler;
|
std::array<OGLSampler, 2> samplers;
|
||||||
|
|
||||||
/// Display information for top and bottom screens respectively
|
// Display information for top and bottom screens respectively
|
||||||
std::array<ScreenInfo, 3> screen_infos;
|
std::array<ScreenInfo, 3> screen_infos;
|
||||||
|
|
||||||
// Shader uniform location indices
|
// Shader uniform location indices
|
||||||
|
|
Reference in New Issue