mirror of
https://github.com/PabloMK7/citra.git
synced 2025-09-11 13:20:04 +00:00
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
23 changed files with 734 additions and 809 deletions
|
@ -7,7 +7,7 @@
|
|||
#include <utility>
|
||||
#include "core/frontend/emu_window.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 {
|
||||
|
||||
|
|
|
@ -386,21 +386,20 @@ bool RasterizerOpenGL::Draw(bool accelerate, bool is_indexed) {
|
|||
(write_depth_fb || regs.framebuffer.output_merger.depth_test_enable != 0 ||
|
||||
(has_stencil && state.stencil.test_enabled));
|
||||
|
||||
const Framebuffer framebuffer =
|
||||
res_cache.GetFramebufferSurfaces(using_color_fb, using_depth_fb);
|
||||
const bool has_color = framebuffer.HasAttachment(SurfaceType::Color);
|
||||
if (!has_color && shadow_rendering) {
|
||||
const auto fb_helper = res_cache.GetFramebufferSurfaces(using_color_fb, using_depth_fb);
|
||||
const Framebuffer* framebuffer = fb_helper.Framebuffer();
|
||||
if (!framebuffer->color_id && framebuffer->shadow_rendering) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Bind the framebuffer surfaces
|
||||
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
|
||||
const auto viewport = framebuffer.Viewport();
|
||||
const auto viewport = fb_helper.Viewport();
|
||||
state.viewport.x = static_cast<GLint>(viewport.x);
|
||||
state.viewport.y = static_cast<GLint>(viewport.y);
|
||||
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.
|
||||
// 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.x = draw_rect.left;
|
||||
state.scissor.y = draw_rect.bottom;
|
||||
state.scissor.width = draw_rect.GetWidth();
|
||||
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
|
||||
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 ||
|
||||
uniform_block_data.data.scissor_x2 != scissor_x2 ||
|
||||
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);
|
||||
}
|
||||
|
||||
res_cache.InvalidateFramebuffer(framebuffer);
|
||||
use_custom_normal = false;
|
||||
|
||||
return succeeded;
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncTextureUnits(const Framebuffer& framebuffer) {
|
||||
void RasterizerOpenGL::SyncTextureUnits(const Framebuffer* framebuffer) {
|
||||
using TextureType = Pica::TexturingRegs::TextureConfig::TextureType;
|
||||
|
||||
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) {
|
||||
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();
|
||||
if (!is_feedback_loop) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make a temporary copy of the framebuffer to sample from
|
||||
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();
|
||||
state.texture_units[texture_index].texture_2d = surface.CopyHandle();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -93,7 +93,7 @@ private:
|
|||
void SyncAndUploadLUTsLF();
|
||||
|
||||
/// 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
|
||||
void BindShadowCube(const Pica::TexturingRegs::FullTextureConfig& texture);
|
||||
|
@ -102,7 +102,7 @@ private:
|
|||
void BindTextureCube(const Pica::TexturingRegs::FullTextureConfig& texture);
|
||||
|
||||
/// 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
|
||||
void UnbindSpecial();
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
// Refer to the license.txt file included.
|
||||
|
||||
#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_vars.h"
|
||||
|
||||
|
|
194
src/video_core/renderer_opengl/gl_texture_mailbox.cpp
Normal file
194
src/video_core/renderer_opengl/gl_texture_mailbox.cpp
Normal file
|
@ -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
|
92
src/video_core/renderer_opengl/gl_texture_mailbox.h
Normal file
92
src/video_core/renderer_opengl/gl_texture_mailbox.h
Normal file
|
@ -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/settings.h"
|
||||
#include "video_core/custom_textures/material.h"
|
||||
#include "video_core/regs.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/renderer_opengl/gl_driver.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/pica_to_gl.h"
|
||||
|
||||
|
@ -22,6 +22,8 @@ using VideoCore::SurfaceFlagBits;
|
|||
using VideoCore::SurfaceType;
|
||||
using VideoCore::TextureType;
|
||||
|
||||
constexpr GLenum TEMP_UNIT = GL_TEXTURE15;
|
||||
|
||||
constexpr FormatTuple DEFAULT_TUPLE = {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE};
|
||||
|
||||
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},
|
||||
}};
|
||||
|
||||
struct FramebufferInfo {
|
||||
GLuint color;
|
||||
GLuint depth;
|
||||
u32 color_level;
|
||||
u32 depth_level;
|
||||
};
|
||||
|
||||
[[nodiscard]] GLbitfield MakeBufferMask(SurfaceType type) {
|
||||
switch (type) {
|
||||
case SurfaceType::Color:
|
||||
|
@ -128,9 +123,8 @@ TextureRuntime::TextureRuntime(const Driver& driver_, VideoCore::RendererBase& r
|
|||
|
||||
TextureRuntime::~TextureRuntime() = default;
|
||||
|
||||
void TextureRuntime::Reset() {
|
||||
alloc_cache.clear();
|
||||
framebuffer_cache.clear();
|
||||
u32 TextureRuntime::RemoveThreshold() {
|
||||
return SWAP_CHAIN_SIZE;
|
||||
}
|
||||
|
||||
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 {
|
||||
if (pixel_format == PixelFormat::Invalid) {
|
||||
return DEFAULT_TUPLE;
|
||||
}
|
||||
|
||||
const auto type = GetFormatType(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];
|
||||
}
|
||||
|
||||
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,
|
||||
const VideoCore::TextureBlit& blit) {
|
||||
const PixelFormat src_format = source.pixel_format;
|
||||
|
@ -353,40 +283,90 @@ void TextureRuntime::GenerateMipmaps(Surface& surface) {
|
|||
}
|
||||
|
||||
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) {
|
||||
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() {
|
||||
if (pixel_format == PixelFormat::Invalid || !alloc) {
|
||||
Surface::Surface(TextureRuntime& runtime, const VideoCore::SurfaceBase& surface,
|
||||
const VideoCore::Material* mat)
|
||||
: SurfaceBase{surface}, tuple{runtime.GetFormatTuple(mat->format)} {
|
||||
if (mat && !driver->IsCustomFormatSupported(mat->format)) {
|
||||
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,
|
||||
const VideoCore::StagingData& staging) {
|
||||
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_height = upload.texture_rect.GetHeight();
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, unscaled_width);
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glActiveTexture(TEMP_UNIT);
|
||||
glBindTexture(GL_TEXTURE_2D, Handle(0));
|
||||
|
||||
const auto& tuple = alloc.tuple;
|
||||
glTexSubImage2D(GL_TEXTURE_2D, upload.texture_level, upload.texture_rect.left,
|
||||
upload.texture_rect.bottom, unscaled_width, unscaled_height, tuple.format,
|
||||
tuple.type, staging.mapped.data());
|
||||
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
glBindTexture(GL_TEXTURE_2D, old_tex);
|
||||
|
||||
const VideoCore::TextureBlit blit = {
|
||||
.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) {
|
||||
const GLuint old_tex = OpenGLState::GetCurState().texture_units[0].texture_2d;
|
||||
const auto& tuple = alloc.tuple;
|
||||
const u32 width = material->width;
|
||||
const u32 height = material->height;
|
||||
const auto color = material->textures[0];
|
||||
const Common::Rectangle filter_rect{0U, height, width, 0U};
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glActiveTexture(TEMP_UNIT);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, width);
|
||||
|
||||
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);
|
||||
glBindTexture(GL_TEXTURE_2D, old_tex);
|
||||
}
|
||||
|
||||
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 u32 unscaled_width = download.texture_rect.GetWidth();
|
||||
|
||||
glActiveTexture(TEMP_UNIT);
|
||||
glPixelStorei(GL_PACK_ROW_LENGTH, unscaled_width);
|
||||
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) {
|
||||
const VideoCore::CustomPixelFormat format{mat->format};
|
||||
if (!driver->IsCustomFormatSupported(format)) {
|
||||
return false;
|
||||
void Surface::ScaleUp(u32 new_scale) {
|
||||
if (res_scale == new_scale || new_scale == 1) {
|
||||
return;
|
||||
}
|
||||
runtime->Recycle(MakeTag(), std::move(alloc));
|
||||
|
||||
SurfaceParams params = *this;
|
||||
params.width = mat->width;
|
||||
params.height = mat->height;
|
||||
params.custom_format = mat->format;
|
||||
alloc = runtime->Allocate(params, mat);
|
||||
res_scale = new_scale;
|
||||
textures[1] = MakeHandle(GL_TEXTURE_2D, GetScaledWidth(), GetScaledHeight(), levels, tuple,
|
||||
DebugName(true));
|
||||
|
||||
LOG_DEBUG(Render_OpenGL, "Swapped {}x{} {} surface at address {:#x} to {}x{} {}",
|
||||
GetScaledWidth(), GetScaledHeight(), VideoCore::PixelFormatAsString(pixel_format),
|
||||
addr, width, height, VideoCore::CustomPixelFormatAsString(format));
|
||||
|
||||
custom_format = format;
|
||||
material = mat;
|
||||
|
||||
return true;
|
||||
VideoCore::TextureBlit blit = {
|
||||
.src_rect = GetRect(),
|
||||
.dst_rect = GetScaledRect(),
|
||||
};
|
||||
for (u32 level = 0; level < levels; level++) {
|
||||
blit.src_level = level;
|
||||
blit.dst_level = level;
|
||||
BlitScale(blit, true);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
HostTextureTag Surface::MakeTag() const noexcept {
|
||||
return HostTextureTag{
|
||||
.width = alloc.width,
|
||||
.height = alloc.height,
|
||||
.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 VideoCore::FramebufferParams& params,
|
||||
const Surface* color, const Surface* depth)
|
||||
: VideoCore::FramebufferParams{params}, res_scale{color ? color->res_scale
|
||||
: (depth ? depth->res_scale : 1u)} {
|
||||
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
|
@ -619,33 +578,15 @@ Framebuffer::Framebuffer(TextureRuntime& runtime, const Surface* color, u32 colo
|
|||
if (color) {
|
||||
attachments[0] = color->Handle();
|
||||
}
|
||||
if (depth_stencil) {
|
||||
attachments[1] = depth_stencil->Handle();
|
||||
if (depth) {
|
||||
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();
|
||||
handle = it->second.handle;
|
||||
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer.handle);
|
||||
SCOPE_EXIT({ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, old_fbo); });
|
||||
OpenGLState state = OpenGLState::GetCurState();
|
||||
state.draw.draw_framebuffer = framebuffer.handle;
|
||||
state.Apply();
|
||||
|
||||
if (shadow_rendering) {
|
||||
glFramebufferParameteri(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_WIDTH,
|
||||
|
@ -658,13 +599,13 @@ Framebuffer::Framebuffer(TextureRuntime& runtime, const Surface* color, u32 colo
|
|||
} else {
|
||||
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
|
||||
color ? color->Handle() : 0, color_level);
|
||||
if (depth_stencil) {
|
||||
if (has_stencil) {
|
||||
if (depth) {
|
||||
if (depth->pixel_format == PixelFormat::D24S8) {
|
||||
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
|
||||
GL_TEXTURE_2D, depth_stencil->Handle(), depth_level);
|
||||
GL_TEXTURE_2D, depth->Handle(), depth_level);
|
||||
} else {
|
||||
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,
|
||||
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 Driver;
|
||||
|
||||
|
@ -82,8 +42,8 @@ public:
|
|||
explicit TextureRuntime(const Driver& driver, VideoCore::RendererBase& renderer);
|
||||
~TextureRuntime();
|
||||
|
||||
/// Clears all cached runtime resources
|
||||
void Reset();
|
||||
/// Returns the removal threshold ticks for the garbage collector
|
||||
u32 RemoveThreshold();
|
||||
|
||||
/// Returns true if the provided pixel format cannot be used natively by the runtime.
|
||||
bool NeedsConversion(VideoCore::PixelFormat pixel_format) const;
|
||||
|
@ -111,13 +71,6 @@ public:
|
|||
void GenerateMipmaps(Surface& surface);
|
||||
|
||||
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
|
||||
const Driver& GetDriver() const {
|
||||
return driver;
|
||||
|
@ -127,8 +80,6 @@ private:
|
|||
const Driver& driver;
|
||||
BlitHelper blit_helper;
|
||||
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> read_fbos;
|
||||
};
|
||||
|
@ -136,6 +87,8 @@ private:
|
|||
class Surface : public VideoCore::SurfaceBase {
|
||||
public:
|
||||
explicit Surface(TextureRuntime& runtime, const VideoCore::SurfaceParams& params);
|
||||
explicit Surface(TextureRuntime& runtime, const VideoCore::SurfaceBase& surface,
|
||||
const VideoCore::Material* material);
|
||||
~Surface();
|
||||
|
||||
Surface(const Surface&) = delete;
|
||||
|
@ -144,13 +97,15 @@ public:
|
|||
Surface(Surface&& o) noexcept = default;
|
||||
Surface& operator=(Surface&& o) noexcept = default;
|
||||
|
||||
[[nodiscard]] GLuint Handle(u32 index = 1) const noexcept {
|
||||
return alloc.handles[index];
|
||||
[[nodiscard]] const FormatTuple& Tuple() const noexcept {
|
||||
return tuple;
|
||||
}
|
||||
|
||||
[[nodiscard]] const FormatTuple& Tuple() const noexcept {
|
||||
return alloc.tuple;
|
||||
}
|
||||
/// Returns the texture handle at index, otherwise the first one if not valid.
|
||||
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
|
||||
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
|
||||
void Attach(GLenum target, u32 level, u32 layer, bool scaled = true);
|
||||
|
||||
/// Swaps the internal allocation to match the provided material
|
||||
bool Swap(const VideoCore::Material* material);
|
||||
/// Scales up the surface to match the new resolution scale.
|
||||
void ScaleUp(u32 new_scale);
|
||||
|
||||
/// Returns the bpp of the internal surface format
|
||||
u32 GetInternalBytesPerPixel() const;
|
||||
|
@ -179,24 +134,32 @@ private:
|
|||
bool DownloadWithoutFbo(const VideoCore::BufferTextureCopy& download,
|
||||
const VideoCore::StagingData& staging);
|
||||
|
||||
/// Returns the texture tag of the current allocation
|
||||
HostTextureTag MakeTag() const noexcept;
|
||||
|
||||
private:
|
||||
const Driver* driver;
|
||||
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:
|
||||
explicit 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);
|
||||
explicit Framebuffer(TextureRuntime& runtime, const VideoCore::FramebufferParams& params,
|
||||
const Surface* color, const Surface* depth_stencil);
|
||||
~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 {
|
||||
return handle;
|
||||
return framebuffer.handle;
|
||||
}
|
||||
|
||||
[[nodiscard]] GLuint Attachment(VideoCore::SurfaceType type) const noexcept {
|
||||
|
@ -208,8 +171,9 @@ public:
|
|||
}
|
||||
|
||||
private:
|
||||
u32 res_scale{1};
|
||||
std::array<GLuint, 2> attachments{};
|
||||
GLuint handle{};
|
||||
OGLFramebuffer framebuffer;
|
||||
};
|
||||
|
||||
class Sampler {
|
||||
|
|
|
@ -2,20 +2,18 @@
|
|||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <queue>
|
||||
#include "common/logging/log.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "common/settings.h"
|
||||
#include "core/core.h"
|
||||
#include "core/dumping/backend.h"
|
||||
#include "core/frontend/emu_window.h"
|
||||
#include "core/frontend/framebuffer_layout.h"
|
||||
#include "core/hw/hw.h"
|
||||
#include "core/hw/lcd.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_state.h"
|
||||
#include "video_core/renderer_opengl/gl_texture_mailbox.h"
|
||||
#include "video_core/renderer_opengl/gl_vars.h"
|
||||
#include "video_core/renderer_opengl/post_processing_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_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.
|
||||
*/
|
||||
|
@ -559,8 +331,15 @@ void RendererOpenGL::InitOpenGLObjects() {
|
|||
glClearColor(Settings::values.bg_red.GetValue(), Settings::values.bg_green.GetValue(),
|
||||
Settings::values.bg_blue.GetValue(), 0.0f);
|
||||
|
||||
filter_sampler.Create();
|
||||
ReloadSampler();
|
||||
for (size_t i = 0; i < samplers.size(); i++) {
|
||||
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();
|
||||
|
||||
|
@ -608,15 +387,6 @@ void RendererOpenGL::InitOpenGLObjects() {
|
|||
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() {
|
||||
// Link shaders and get variable locations
|
||||
std::string shader_data;
|
||||
|
@ -793,13 +563,14 @@ void RendererOpenGL::DrawSingleScreen(const ScreenInfo& screen_info, float x, fl
|
|||
}
|
||||
|
||||
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),
|
||||
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.height * scale_factor));
|
||||
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].sampler = filter_sampler.handle;
|
||||
state.texture_units[0].sampler = sampler;
|
||||
state.Apply();
|
||||
|
||||
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 GLuint sampler = samplers[Settings::values.filter_mode.GetValue()].handle;
|
||||
glUniform4f(uniform_i_resolution,
|
||||
static_cast<float>(screen_info_l.texture.width * 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);
|
||||
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[0].sampler = filter_sampler.handle;
|
||||
state.texture_units[1].sampler = filter_sampler.handle;
|
||||
state.texture_units[0].sampler = sampler;
|
||||
state.texture_units[1].sampler = sampler;
|
||||
state.Apply();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if (settings.sampler_update_requested.exchange(false)) {
|
||||
// Set the new filtering mode for the sampler
|
||||
ReloadSampler();
|
||||
}
|
||||
|
||||
if (settings.shader_update_requested.exchange(false)) {
|
||||
// Update fragment shader before drawing
|
||||
shader.Release();
|
||||
|
@ -1119,7 +886,7 @@ void RendererOpenGL::TryPresent(int timeout_ms, bool is_secondary) {
|
|||
void RendererOpenGL::PrepareVideoDumping() {
|
||||
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;
|
||||
}
|
||||
frame_dumper.StartDumping();
|
||||
|
@ -1129,7 +896,7 @@ void RendererOpenGL::CleanupVideoDumping() {
|
|||
frame_dumper.StopDumping();
|
||||
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->free_cv.notify_one();
|
||||
|
|
|
@ -21,20 +21,6 @@ namespace Core {
|
|||
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 {
|
||||
|
||||
/// Structure used for storing information about the textures for each 3DS screen
|
||||
|
@ -72,7 +58,6 @@ public:
|
|||
|
||||
private:
|
||||
void InitOpenGLObjects();
|
||||
void ReloadSampler();
|
||||
void ReloadShader();
|
||||
void PrepareRendertarget();
|
||||
void RenderScreenshot();
|
||||
|
@ -109,9 +94,9 @@ private:
|
|||
OGLBuffer vertex_buffer;
|
||||
OGLProgram shader;
|
||||
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;
|
||||
|
||||
// Shader uniform location indices
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue