video_core: Make renderer common
* Also rename to DisplayRenderer
This commit is contained in:
@ -35,6 +35,8 @@ add_library(video_core STATIC
|
||||
common/rasterizer.h
|
||||
common/rasterizer_cache.cpp
|
||||
common/rasterizer_cache.h
|
||||
common/renderer.cpp
|
||||
common/renderer.h
|
||||
common/shader_runtime_cache.h
|
||||
common/shader_disk_cache.cpp
|
||||
common/shader_disk_cache.h
|
||||
|
@ -14,7 +14,10 @@ class EmuWindow;
|
||||
|
||||
namespace VideoCore {
|
||||
|
||||
class ShaderDiskCache;
|
||||
// A piece of information the video frontend can query the backend about
|
||||
enum class Query {
|
||||
PresentFormat = 0
|
||||
};
|
||||
|
||||
// Common interface of a video backend
|
||||
class BackendBase {
|
||||
@ -22,8 +25,17 @@ public:
|
||||
BackendBase(Frontend::EmuWindow& window) : window(window) {}
|
||||
virtual ~BackendBase() = default;
|
||||
|
||||
// Acquires the next swapchain images and begins rendering
|
||||
virtual bool BeginPresent() = 0;
|
||||
|
||||
// Triggers a swapchain buffer swap
|
||||
virtual void SwapBuffers();
|
||||
virtual void EndPresent() = 0;
|
||||
|
||||
// Returns the framebuffer created from the swapchain images
|
||||
virtual FramebufferHandle GetWindowFramebuffer() = 0;
|
||||
|
||||
// Asks the driver about a particular piece of information
|
||||
virtual u64 QueryDriver(Query query) = 0;
|
||||
|
||||
// Creates a backend specific texture handle
|
||||
virtual TextureHandle CreateTexture(TextureInfo info) = 0;
|
||||
@ -60,7 +72,7 @@ public:
|
||||
// Executes a compute shader
|
||||
virtual void DispatchCompute(PipelineHandle pipeline, Common::Vec3<u32> groupsize,
|
||||
Common::Vec3<u32> groups) = 0;
|
||||
private:
|
||||
protected:
|
||||
Frontend::EmuWindow& window;
|
||||
};
|
||||
|
||||
|
@ -39,6 +39,8 @@ enum class BindingType : u32 {
|
||||
|
||||
using BindingGroup = BitFieldArray<0, 3, MAX_BINDINGS_IN_GROUP, BindingType>;
|
||||
|
||||
static_assert(sizeof(BindingGroup));
|
||||
|
||||
/**
|
||||
* Describes all the resources used in the pipeline
|
||||
*/
|
||||
@ -48,6 +50,8 @@ struct PipelineLayoutInfo {
|
||||
u8 push_constant_block_size = 0;
|
||||
};
|
||||
|
||||
static_assert(sizeof(PipelineLayoutInfo));
|
||||
|
||||
/**
|
||||
* The pipeline state is tightly packed with bitfields to reduce
|
||||
* the overhead of hashing as much as possible
|
||||
@ -83,7 +87,8 @@ union BlendState {
|
||||
BitField<16, 4, Pica::BlendFactor> dst_alpha_blend_factor;
|
||||
BitField<20, 3, Pica::BlendEquation> alpha_blend_eq;
|
||||
BitField<23, 4, u32> color_write_mask;
|
||||
BitField<27, 4, Pica::LogicOp> logic_op;
|
||||
BitField<27, 1, u32> logic_op_enable;
|
||||
BitField<28, 4, Pica::LogicOp> logic_op;
|
||||
};
|
||||
|
||||
enum class AttribType : u32 {
|
||||
@ -159,11 +164,14 @@ public:
|
||||
// Binds the sampler in the specified slot
|
||||
virtual void BindSampler(u32 group, u32 slot, SamplerHandle handle) = 0;
|
||||
|
||||
// Binds a small uniform block (under 256 bytes) to the current pipeline
|
||||
virtual void BindPushConstant(std::span<const std::byte> data) = 0;
|
||||
|
||||
// Sets the viewport of the pipeline
|
||||
virtual void SetViewport(Rect2D viewport) = 0;
|
||||
virtual void SetViewport(float x, float y, float width, float height) = 0;
|
||||
|
||||
// Sets the scissor of the pipeline
|
||||
virtual void SetScissor(Rect2D scissor) = 0;
|
||||
virtual void SetScissor(s32 x, s32 y, u32 width, u32 height) = 0;
|
||||
|
||||
// Returns the pipeline type (Graphics or Compute)
|
||||
PipelineType GetType() const {
|
||||
@ -171,7 +179,7 @@ public:
|
||||
}
|
||||
|
||||
protected:
|
||||
PipelineInfo info;
|
||||
const PipelineInfo info;
|
||||
PipelineType type = PipelineType::Graphics;
|
||||
};
|
||||
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include "core/hw/gpu.h"
|
||||
#include "video_core/pica_state.h"
|
||||
#include "video_core/common/rasterizer.h"
|
||||
#include "video_core/common/renderer.h"
|
||||
#include "video_core/common/pipeline_cache.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
@ -78,7 +79,7 @@ constexpr VertexLayout HardwareVertex::GetVertexLayout() {
|
||||
constexpr std::array sizes = {4, 4, 2, 2, 2, 1, 4, 3};
|
||||
u32 offset = 0;
|
||||
|
||||
for (u32 loc = 0; loc < layout.attribute_count; loc++) {
|
||||
for (u32 loc = 0; loc < 8; loc++) {
|
||||
VertexAttribute& attribute = layout.attributes[loc];
|
||||
attribute.binding.Assign(0);
|
||||
attribute.location.Assign(loc);
|
||||
@ -1559,7 +1560,7 @@ bool Rasterizer::AccelerateDisplay(const GPU::Regs::FramebufferConfig& config,
|
||||
(float)src_rect.bottom / (float)scaled_height, (float)src_rect.left / (float)scaled_width,
|
||||
(float)src_rect.top / (float)scaled_height, (float)src_rect.right / (float)scaled_width);
|
||||
|
||||
screen_info.display_texture = src_surface->texture.handle;
|
||||
screen_info.display_texture = src_surface->texture;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -38,6 +38,7 @@ struct HardwareVertex {
|
||||
};
|
||||
|
||||
class BackendBase;
|
||||
struct ScreenInfo;
|
||||
|
||||
class Rasterizer {
|
||||
public:
|
||||
|
@ -241,9 +241,11 @@ bool RasterizerCache::FillSurface(const Surface& surface, const u8* fill_data, C
|
||||
.depth_stencil = depth_surface ? surface->texture : TextureHandle{}
|
||||
};
|
||||
|
||||
// Some backends (Vulkan) provide texture clear functions but in general
|
||||
// it's still more efficient to use framebuffers for fills to take advantage
|
||||
// of the dedicated clear engine on the GPU
|
||||
/**
|
||||
* Some backends (for example Vulkan) provide texture clear functions but in general
|
||||
* it's still more efficient to use framebuffers for fills to take advantage of the dedicated
|
||||
* clear engine on the GPU
|
||||
*/
|
||||
FramebufferHandle framebuffer;
|
||||
if (auto iter = framebuffer_cache.find(framebuffer_info); iter != framebuffer_cache.end()) {
|
||||
framebuffer = iter->second;
|
||||
@ -258,7 +260,7 @@ bool RasterizerCache::FillSurface(const Surface& surface, const u8* fill_data, C
|
||||
if (surface->type == SurfaceType::Color || surface->type == SurfaceType::Texture) {
|
||||
Pica::Texture::TextureInfo tex_info{};
|
||||
tex_info.format = static_cast<Pica::TexturingRegs::TextureFormat>(surface->pixel_format);
|
||||
Common::Vec4f color_values = Pica::Texture::LookupTexture(fill_data, 0, 0, tex_info) / 255.f;
|
||||
const auto color_values = Pica::Texture::LookupTexture(fill_data, 0, 0, tex_info) / 255.f;
|
||||
|
||||
framebuffer->DoClear(color_values, 0.0f, 0);
|
||||
} else if (surface->type == SurfaceType::Depth) {
|
||||
@ -286,6 +288,7 @@ bool RasterizerCache::FillSurface(const Surface& surface, const u8* fill_data, C
|
||||
|
||||
framebuffer->DoClear({}, depth_float, stencil_int);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
508
src/video_core/common/renderer.cpp
Normal file
508
src/video_core/common/renderer.cpp
Normal file
@ -0,0 +1,508 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/frontend/emu_window.h"
|
||||
#include "core/frontend/framebuffer_layout.h"
|
||||
#include "core/hw/gpu.h"
|
||||
#include "core/hw/hw.h"
|
||||
#include "core/hw/lcd.h"
|
||||
#include "core/settings.h"
|
||||
#include "video_core/common/renderer.h"
|
||||
#include "video_core/common/rasterizer.h"
|
||||
#include "video_core/renderer_vulkan/vk_backend.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
namespace VideoCore {
|
||||
|
||||
static std::string vertex_shader_source = R"(
|
||||
#version 450 core
|
||||
layout (location = 0) in vec2 vert_position;
|
||||
layout (location = 1) in vec2 vert_tex_coord;
|
||||
layout (location = 0) out vec2 frag_tex_coord;
|
||||
|
||||
layout (std140, push_constant) uniform PresentUniformData {
|
||||
mat4 modelview_matrix;
|
||||
vec4 i_resolution;
|
||||
vec4 o_resolution;
|
||||
int screen_id;
|
||||
int layer;
|
||||
int reverse_interlaced;
|
||||
};
|
||||
|
||||
void main() {
|
||||
vec4 position = vec4(vert_position, 0.0, 1.0) * modelview_matrix;
|
||||
gl_Position = vec4(position.x, -position.y, 0.0, 1.0);
|
||||
frag_tex_coord = vert_tex_coord;
|
||||
}
|
||||
)";
|
||||
|
||||
static std::string fragment_shader_source = R"(
|
||||
layout (location = 0) in vec2 frag_tex_coord;
|
||||
layout (location = 0) out vec4 color;
|
||||
layout (set = 0, binding = 0) uniform texture2D top_screen;
|
||||
|
||||
layout (std140, push_constant) uniform PresentUniformData {
|
||||
mat4 modelview_matrix;
|
||||
vec4 i_resolution;
|
||||
vec4 o_resolution;
|
||||
int screen_id;
|
||||
int layer;
|
||||
int reverse_interlaced;
|
||||
};
|
||||
|
||||
void main() {
|
||||
color = texture(top_screen, frag_tex_coord);
|
||||
}
|
||||
)";
|
||||
|
||||
static std::string fragment_shader_anaglyph_source = R"(
|
||||
|
||||
// Anaglyph Red-Cyan shader based on Dubois algorithm
|
||||
// Constants taken from the paper:
|
||||
// "Conversion of a Stereo Pair to Anaglyph with
|
||||
// the Least-Squares Projection Method"
|
||||
// Eric Dubois, March 2009
|
||||
const mat3 l = mat3(0.437, 0.449, 0.164,
|
||||
-0.062,-0.062,-0.024,
|
||||
-0.048,-0.050,-0.017);
|
||||
const mat3 r = mat3(-0.011,-0.032,-0.007,
|
||||
0.377, 0.761, 0.009,
|
||||
-0.026,-0.093, 1.234);
|
||||
|
||||
layout (location = 0) in vec2 frag_tex_coord;
|
||||
layout (location = 0) out vec4 color;
|
||||
layout (set = 0, binding = 0) uniform sampler2D top_screen;
|
||||
layout (set = 0, binding = 1) uniform sampler2D top_screen_r;
|
||||
|
||||
void main() {
|
||||
vec4 color_tex_l = texture(top_screen, frag_tex_coord);
|
||||
vec4 color_tex_r = texture(top_screen_r, frag_tex_coord);
|
||||
color = vec4(color_tex_l.rgb * l + color_tex_r.rgb * r, color_tex_l.a);
|
||||
}
|
||||
)";
|
||||
|
||||
static std::string fragment_shader_interlaced_source = R"(
|
||||
|
||||
layout (location = 0) in vec2 frag_tex_coord;
|
||||
layout (location = 0) out vec4 color;
|
||||
|
||||
layout (std140, push_constant) uniform PresentUniformData {
|
||||
mat4 modelview_matrix;
|
||||
vec4 i_resolution;
|
||||
vec4 o_resolution;
|
||||
int layer;
|
||||
int reverse_interlaced;
|
||||
};
|
||||
|
||||
layout (set = 0, binding = 0) uniform sampler2D top_screen;
|
||||
layout (set = 0, binding = 1) uniform sampler2D top_screen_r;
|
||||
|
||||
void main() {
|
||||
float screen_row = o_resolution.x * frag_tex_coord.x;
|
||||
if (int(screen_row) % 2 == reverse_interlaced) {
|
||||
color = texture(top_screen, frag_tex_coord);
|
||||
} else {
|
||||
color = texture(top_screen_r, frag_tex_coord);
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
constexpr VertexLayout ScreenRectVertex::GetVertexLayout() {
|
||||
VertexLayout layout{};
|
||||
layout.attribute_count = 2;
|
||||
layout.binding_count = 1;
|
||||
|
||||
// Define binding
|
||||
layout.bindings[0].binding.Assign(0);
|
||||
layout.bindings[0].fixed.Assign(0);
|
||||
layout.bindings[0].stride.Assign(sizeof(ScreenRectVertex));
|
||||
|
||||
// Define the attributes
|
||||
for (u32 loc = 0; loc < 2; loc++) {
|
||||
layout.attributes[loc].binding.Assign(0);
|
||||
layout.attributes[loc].location.Assign(loc);
|
||||
layout.attributes[loc].offset.Assign(loc * sizeof(glm::vec2));
|
||||
layout.attributes[loc].size.Assign(2);
|
||||
layout.attributes[loc].type.Assign(AttribType::Float);
|
||||
}
|
||||
|
||||
return layout;
|
||||
}
|
||||
|
||||
// Renderer pipeline layout
|
||||
static constexpr PipelineLayoutInfo RENDERER_PIPELINE_INFO = {
|
||||
.group_count = 2,
|
||||
.binding_groups = {
|
||||
BindingGroup{
|
||||
BindingType::Texture, // Top screen
|
||||
BindingType::Texture // Top screen stereo pair
|
||||
},
|
||||
BindingGroup{
|
||||
BindingType::Sampler
|
||||
}
|
||||
},
|
||||
.push_constant_block_size = sizeof(PresentUniformData)
|
||||
};
|
||||
|
||||
DisplayRenderer::DisplayRenderer(Frontend::EmuWindow& window) : render_window(window) {
|
||||
//window.mailbox = nullptr;
|
||||
backend = std::make_unique<Vulkan::Backend>(window);
|
||||
rasterizer = std::make_unique<VideoCore::Rasterizer>(window, backend);
|
||||
|
||||
// Create vertex buffer for the screen rectangle
|
||||
const BufferInfo vertex_info = {
|
||||
.capacity = sizeof(ScreenRectVertex) * 10,
|
||||
.usage = BufferUsage::Vertex
|
||||
};
|
||||
|
||||
vertex_buffer = backend->CreateBuffer(vertex_info);
|
||||
|
||||
const std::array fragment_shaders = {&fragment_shader_source,
|
||||
&fragment_shader_anaglyph_source,
|
||||
&fragment_shader_interlaced_source};
|
||||
|
||||
const auto color_format = static_cast<TextureFormat>(backend->QueryDriver(Query::PresentFormat));
|
||||
PipelineInfo present_pipeline_info = {
|
||||
.vertex_layout = ScreenRectVertex::GetVertexLayout(),
|
||||
.layout = RENDERER_PIPELINE_INFO,
|
||||
.color_attachment = color_format,
|
||||
.depth_attachment = TextureFormat::Undefined
|
||||
};
|
||||
|
||||
// Set topology to strip
|
||||
present_pipeline_info.rasterization.topology.Assign(Pica::TriangleTopology::Strip);
|
||||
|
||||
|
||||
// Create vertex and fragment shaders
|
||||
vertex_shader = backend->CreateShader(ShaderStage::Vertex, "Present vertex shader",
|
||||
vertex_shader_source);
|
||||
for (int i = 0; i < PRESENT_PIPELINES; i++) {
|
||||
const std::string name = fmt::format("Present shader {:d}", i);
|
||||
present_shaders[i] = backend->CreateShader(ShaderStage::Fragment, name, *fragment_shaders[i]);
|
||||
|
||||
// Create associated pipeline
|
||||
present_pipeline_info.shaders[0] = vertex_shader;
|
||||
present_pipeline_info.shaders[1] = present_shaders[i];
|
||||
present_pipelines[i] = backend->CreatePipeline(PipelineType::Graphics, present_pipeline_info);
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayRenderer::PrepareRendertarget() {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
int fb_id = i == 2 ? 1 : 0;
|
||||
const auto& framebuffer = GPU::g_regs.framebuffer_config[fb_id];
|
||||
|
||||
// Main LCD (0): 0x1ED02204, Sub LCD (1): 0x1ED02A04
|
||||
u32 lcd_color_addr = (fb_id == 0) ? LCD_REG_INDEX(color_fill_top) : LCD_REG_INDEX(color_fill_bottom);
|
||||
lcd_color_addr = HW::VADDR_LCD + 4 * lcd_color_addr;
|
||||
LCD::Regs::ColorFill color_fill = {0};
|
||||
LCD::Read(color_fill.raw, lcd_color_addr);
|
||||
|
||||
if (color_fill.is_enabled) {
|
||||
LoadColorToActiveTexture(color_fill.color_r, color_fill.color_g, color_fill.color_b, screen_infos[i]);
|
||||
} else {
|
||||
const TextureHandle& texture = screen_infos[i].texture;
|
||||
u32 fwidth = framebuffer.width;
|
||||
u32 fheight = framebuffer.height;
|
||||
|
||||
if (texture->GetWidth() != fwidth || texture->GetHeight() != fheight ||
|
||||
screen_infos[i].format != framebuffer.color_format) {
|
||||
// Reallocate texture if the framebuffer size has changed.
|
||||
// This is expected to not happen very often and hence should not be a
|
||||
// performance problem.
|
||||
ConfigureFramebufferTexture(screen_infos[i], framebuffer);
|
||||
}
|
||||
|
||||
LoadFBToScreenInfo(framebuffer, screen_infos[i], i == 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayRenderer::LoadFBToScreenInfo(const GPU::Regs::FramebufferConfig& framebuffer,
|
||||
ScreenInfo& screen_info, bool right_eye) {
|
||||
|
||||
if (framebuffer.address_right1 == 0 || framebuffer.address_right2 == 0) {
|
||||
right_eye = false;
|
||||
}
|
||||
|
||||
const PAddr framebuffer_addr = framebuffer.active_fb == 0
|
||||
? (!right_eye ? framebuffer.address_left1 : framebuffer.address_right1)
|
||||
: (!right_eye ? framebuffer.address_left2 : framebuffer.address_right2);
|
||||
|
||||
LOG_TRACE(Render_Vulkan, "0x{:08x} bytes from 0x{:08x}({}x{}), fmt {:x}",
|
||||
framebuffer.stride * framebuffer.height, framebuffer_addr, framebuffer.width.Value(),
|
||||
framebuffer.height.Value(), framebuffer.format);
|
||||
|
||||
int bpp = GPU::Regs::BytesPerPixel(framebuffer.color_format);
|
||||
std::size_t pixel_stride = framebuffer.stride / bpp;
|
||||
|
||||
// OpenGL only supports specifying a stride in units of pixels, not bytes, unfortunately
|
||||
ASSERT(pixel_stride * bpp == framebuffer.stride);
|
||||
|
||||
// Ensure no bad interactions with GL_UNPACK_ALIGNMENT, which by default
|
||||
// only allows rows to have a memory alignement of 4.
|
||||
ASSERT(pixel_stride % 4 == 0);
|
||||
|
||||
if (!rasterizer->AccelerateDisplay(framebuffer, framebuffer_addr, static_cast<u32>(pixel_stride), screen_info)) {
|
||||
ASSERT(false);
|
||||
// Reset the screen info's display texture to its own permanent texture
|
||||
screen_info.display_texture = screen_info.texture;
|
||||
screen_info.display_texcoords = Common::Rectangle<f32>{0.f, 0.f, 1.f, 1.f};
|
||||
|
||||
rasterizer->FlushRegion(framebuffer_addr, framebuffer.stride * framebuffer.height);
|
||||
|
||||
//const u8* data_ptr = VideoCore::g_memory->GetPhysicalPointer(framebuffer_addr);
|
||||
//const u32 data_size = screen_info.texture->GetWidth() * screen_info.texture->GetHeight() *
|
||||
//auto framebuffer_data = std::span<const u8>{data_ptr, screen_info.texture.GetSize()};
|
||||
|
||||
//Rect2D region{0, 0, framebuffer.width, framebuffer.height};
|
||||
//screen_info.texture->Upload(region, pixel_stride, framebuffer_data);
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayRenderer::LoadColorToActiveTexture(u8 color_r, u8 color_g, u8 color_b, const ScreenInfo& screen) {
|
||||
/*state.texture_units[0].texture_2d = texture.resource.handle;
|
||||
state.Apply();
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
u8 framebuffer_data[3] = {color_r, color_g, color_b};
|
||||
|
||||
// Update existing texture
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, framebuffer_data);
|
||||
|
||||
state.texture_units[0].texture_2d = 0;
|
||||
state.Apply();*/
|
||||
}
|
||||
|
||||
void DisplayRenderer::ConfigureFramebufferTexture(ScreenInfo& screen, const GPU::Regs::FramebufferConfig& framebuffer) {
|
||||
screen.format = framebuffer.color_format;
|
||||
|
||||
auto ToTextureFormat = [&screen]() {
|
||||
switch (screen.format) {
|
||||
case GPU::Regs::PixelFormat::RGBA8:
|
||||
return TextureFormat::RGBA8;
|
||||
case GPU::Regs::PixelFormat::RGB8:
|
||||
return TextureFormat::RGB8;
|
||||
case GPU::Regs::PixelFormat::RGB565:
|
||||
return TextureFormat::RGB565;
|
||||
case GPU::Regs::PixelFormat::RGB5A1:
|
||||
return TextureFormat::RGB5A1;
|
||||
case GPU::Regs::PixelFormat::RGBA4:
|
||||
return TextureFormat::RGBA4;
|
||||
default:
|
||||
UNIMPLEMENTED();
|
||||
}
|
||||
};
|
||||
|
||||
const TextureInfo texture_info = {
|
||||
.width = static_cast<u16>(framebuffer.width),
|
||||
.height = static_cast<u16>(framebuffer.height),
|
||||
.levels = 1,
|
||||
.type = TextureType::Texture2D,
|
||||
.view_type = TextureViewType::View2D,
|
||||
.format = ToTextureFormat()
|
||||
};
|
||||
|
||||
screen.texture = backend->CreateTexture(texture_info);
|
||||
}
|
||||
|
||||
void DisplayRenderer::ReloadPresentPipeline() {
|
||||
const auto& render_3d = Settings::values.render_3d;
|
||||
|
||||
// Update current pipeline
|
||||
switch (render_3d) {
|
||||
case Settings::StereoRenderOption::Anaglyph:
|
||||
current_pipeline = present_pipelines[1];
|
||||
break;
|
||||
case Settings::StereoRenderOption::ReverseInterlaced:
|
||||
case Settings::StereoRenderOption::Interlaced:
|
||||
current_pipeline = present_pipelines[2];
|
||||
break;
|
||||
default:
|
||||
current_pipeline = present_pipelines[0];
|
||||
}
|
||||
|
||||
// Update uniform data
|
||||
uniform_data.reverse_interlaced = (render_3d == Settings::StereoRenderOption::ReverseInterlaced);
|
||||
}
|
||||
|
||||
void DisplayRenderer::DrawSingleScreen(u32 screen, bool rotate, float x, float y, float w, float h) {
|
||||
const ScreenInfo& screen_info = screen_infos[screen];
|
||||
const auto& texcoords = screen_info.display_texcoords;
|
||||
|
||||
// Clear the swapchain framebuffer
|
||||
FramebufferHandle display = backend->GetWindowFramebuffer();
|
||||
display->DoClear(clear_color, 0.f, 0);
|
||||
|
||||
// Update viewport and scissor
|
||||
const auto& color_surface = display->GetColorAttachment();
|
||||
current_pipeline->SetViewport(0.f, 0.f, color_surface->GetWidth(), color_surface->GetHeight());
|
||||
current_pipeline->SetScissor(0, 0, color_surface->GetWidth(), color_surface->GetHeight());
|
||||
|
||||
std::array<ScreenRectVertex, 4> vertices;
|
||||
if (rotate) {
|
||||
vertices = {
|
||||
ScreenRectVertex{x, y, texcoords.bottom, texcoords.left},
|
||||
ScreenRectVertex{x + w, y, texcoords.bottom, texcoords.right},
|
||||
ScreenRectVertex{x, y + h, texcoords.top, texcoords.left},
|
||||
ScreenRectVertex{x + w, y + h, texcoords.top, texcoords.right}
|
||||
};
|
||||
} else {
|
||||
vertices = {
|
||||
ScreenRectVertex{x, y, texcoords.bottom, texcoords.right},
|
||||
ScreenRectVertex{x + w, y, texcoords.top, texcoords.right},
|
||||
ScreenRectVertex{x, y + h, texcoords.bottom, texcoords.left},
|
||||
ScreenRectVertex{x + w, y + h, texcoords.top, texcoords.left}
|
||||
};
|
||||
}
|
||||
|
||||
const u32 size = sizeof(ScreenRectVertex) * vertices.size();
|
||||
const u32 mapped_offset = vertex_buffer->GetCurrentOffset();
|
||||
auto vertex_data = vertex_buffer->Map(size);
|
||||
|
||||
// Copy vertex data
|
||||
std::memcpy(vertex_data.data(), vertices.data(), size);
|
||||
vertex_buffer->Commit(size);
|
||||
|
||||
// As this is the "DrawSingleScreenRotated" function, the output resolution dimensions have been
|
||||
// swapped. If a non-rotated draw-screen function were to be added for book-mode games, those
|
||||
// should probably be set to the standard (w, h, 1.0 / w, 1.0 / h) ordering.
|
||||
const u16 scale_factor = VideoCore::GetResolutionScaleFactor();
|
||||
const u32 width = screen_info.texture->GetWidth();
|
||||
const u32 height = screen_info.texture->GetHeight();
|
||||
|
||||
uniform_data.i_resolution = glm::vec4{width * scale_factor, height * scale_factor,
|
||||
1.0f / (width * scale_factor),
|
||||
1.0f / (height * scale_factor)};
|
||||
uniform_data.o_resolution = glm::vec4{h, w, 1.0f / h, 1.0f / w};
|
||||
|
||||
// Upload uniform data
|
||||
current_pipeline->BindPushConstant(uniform_data.AsBytes());
|
||||
|
||||
// Bind the vertex buffer and draw
|
||||
const std::array offsets = {mapped_offset};
|
||||
backend->BindVertexBuffer(vertex_buffer, offsets);
|
||||
backend->Draw(current_pipeline, FramebufferHandle{}, 0, vertices.size());
|
||||
}
|
||||
|
||||
void DisplayRenderer::DrawScreens(bool flipped) {
|
||||
const auto& layout = render_window.GetFramebufferLayout();
|
||||
if (VideoCore::g_renderer_bg_color_update_requested.exchange(false)) {
|
||||
// Update background color before drawing
|
||||
clear_color = Common::Vec4f{Settings::values.bg_red, Settings::values.bg_green,
|
||||
Settings::values.bg_blue, 0.0f};
|
||||
}
|
||||
|
||||
// Set the new filtering mode for the sampler
|
||||
if (VideoCore::g_renderer_sampler_update_requested.exchange(false)) {
|
||||
ReloadSampler();
|
||||
}
|
||||
|
||||
// Update present pipeline before drawing
|
||||
if (VideoCore::g_renderer_shader_update_requested.exchange(false)) {
|
||||
ReloadPresentPipeline();
|
||||
}
|
||||
|
||||
const auto& top_screen = layout.top_screen;
|
||||
//const auto& bottom_screen = layout.bottom_screen;
|
||||
|
||||
// Set projection matrix
|
||||
uniform_data.modelview = glm::transpose(glm::ortho<float>(0.f, layout.width, layout.height, 0.0f, 0.f, 1.f));
|
||||
|
||||
uniform_data.layer = 0;
|
||||
if (layout.top_screen_enabled) {
|
||||
if (Settings::values.render_3d == Settings::StereoRenderOption::Off) {
|
||||
DrawSingleScreen(0, layout.is_rotated, top_screen.left, top_screen.top,
|
||||
top_screen.GetWidth(), top_screen.GetHeight());
|
||||
} else if (Settings::values.render_3d == Settings::StereoRenderOption::SideBySide) {
|
||||
DrawSingleScreen(0, layout.is_rotated, top_screen.left / 2.f, top_screen.top,
|
||||
top_screen.GetWidth() / 2.f, top_screen.GetHeight());
|
||||
uniform_data.layer = 1;
|
||||
DrawSingleScreen(1, layout.is_rotated, (top_screen.left / 2.f) + (layout.width / 2.f), top_screen.top,
|
||||
top_screen.GetWidth() / 2.f, top_screen.GetHeight());
|
||||
} else if (Settings::values.render_3d == Settings::StereoRenderOption::CardboardVR) {
|
||||
DrawSingleScreen(0, layout.is_rotated, layout.top_screen.left, layout.top_screen.top,
|
||||
layout.top_screen.GetWidth(), layout.top_screen.GetHeight());
|
||||
uniform_data.layer = 1;
|
||||
DrawSingleScreen(1, layout.is_rotated, layout.cardboard.top_screen_right_eye + (layout.width / 2.f),
|
||||
layout.top_screen.top, layout.top_screen.GetWidth(), layout.top_screen.GetHeight());
|
||||
}
|
||||
}
|
||||
|
||||
uniform_data.layer = 0;
|
||||
/*if (layout.bottom_screen_enabled) {
|
||||
if (layout.is_rotated) {
|
||||
if (Settings::values.render_3d == Settings::StereoRenderOption::Off) {
|
||||
DrawSingleScreenRotated(2, (float)bottom_screen.left,
|
||||
(float)bottom_screen.top, (float)bottom_screen.GetWidth(),
|
||||
(float)bottom_screen.GetHeight());
|
||||
} else if (Settings::values.render_3d == Settings::StereoRenderOption::SideBySide) {
|
||||
DrawSingleScreenRotated(
|
||||
2, (float)bottom_screen.left / 2, (float)bottom_screen.top,
|
||||
(float)bottom_screen.GetWidth() / 2, (float)bottom_screen.GetHeight());
|
||||
uniform_data.layer = 1;
|
||||
DrawSingleScreenRotated(
|
||||
2, ((float)bottom_screen.left / 2) + ((float)layout.width / 2),
|
||||
(float)bottom_screen.top, (float)bottom_screen.GetWidth() / 2,
|
||||
(float)bottom_screen.GetHeight());
|
||||
} else if (Settings::values.render_3d == Settings::StereoRenderOption::CardboardVR) {
|
||||
DrawSingleScreenRotated(2, layout.bottom_screen.left,
|
||||
layout.bottom_screen.top, layout.bottom_screen.GetWidth(),
|
||||
layout.bottom_screen.GetHeight());
|
||||
uniform_data.layer = 1;
|
||||
DrawSingleScreenRotated(2,
|
||||
layout.cardboard.bottom_screen_right_eye +
|
||||
((float)layout.width / 2),
|
||||
layout.bottom_screen.top, layout.bottom_screen.GetWidth(),
|
||||
layout.bottom_screen.GetHeight());
|
||||
}
|
||||
} else {
|
||||
if (Settings::values.render_3d == Settings::StereoRenderOption::Off) {
|
||||
DrawSingleScreen(2, (float)bottom_screen.left,
|
||||
(float)bottom_screen.top, (float)bottom_screen.GetWidth(),
|
||||
(float)bottom_screen.GetHeight());
|
||||
} else if (Settings::values.render_3d == Settings::StereoRenderOption::SideBySide) {
|
||||
DrawSingleScreen(2, (float)bottom_screen.left / 2,
|
||||
(float)bottom_screen.top, (float)bottom_screen.GetWidth() / 2,
|
||||
(float)bottom_screen.GetHeight());
|
||||
uniform_data.layer = 1;
|
||||
DrawSingleScreen(2,
|
||||
((float)bottom_screen.left / 2) + ((float)layout.width / 2),
|
||||
(float)bottom_screen.top, (float)bottom_screen.GetWidth() / 2,
|
||||
(float)bottom_screen.GetHeight());
|
||||
} else if (Settings::values.render_3d == Settings::StereoRenderOption::CardboardVR) {
|
||||
DrawSingleScreen(2, layout.bottom_screen.left,
|
||||
layout.bottom_screen.top, layout.bottom_screen.GetWidth(),
|
||||
layout.bottom_screen.GetHeight());
|
||||
uniform_data.layer = 1;
|
||||
DrawSingleScreen(2,
|
||||
layout.cardboard.bottom_screen_right_eye +
|
||||
((float)layout.width / 2),
|
||||
layout.bottom_screen.top, layout.bottom_screen.GetWidth(),
|
||||
layout.bottom_screen.GetHeight());
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
void DisplayRenderer::SwapBuffers() {
|
||||
// Configure current framebuffer and recreate swapchain if necessary
|
||||
PrepareRendertarget();
|
||||
|
||||
// Present the 3DS screens
|
||||
if (backend->BeginPresent()) {
|
||||
DrawScreens(false);
|
||||
backend->EndPresent();
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayRenderer::UpdateCurrentFramebufferLayout(bool is_portrait_mode) {
|
||||
const Layout::FramebufferLayout& layout = render_window.GetFramebufferLayout();
|
||||
render_window.UpdateCurrentFramebufferLayout(layout.width, layout.height, is_portrait_mode);
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
143
src/video_core/common/renderer.h
Normal file
143
src/video_core/common/renderer.h
Normal file
@ -0,0 +1,143 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <glm/glm.hpp>
|
||||
#include "common/math_util.h"
|
||||
#include "core/hw/gpu.h"
|
||||
#include "video_core/common/pipeline.h"
|
||||
|
||||
namespace Frontend {
|
||||
class EmuWindow;
|
||||
}
|
||||
|
||||
namespace Layout {
|
||||
struct FramebufferLayout;
|
||||
}
|
||||
|
||||
namespace VideoCore {
|
||||
|
||||
class BackendBase;
|
||||
class Rasterizer;
|
||||
|
||||
// Structure used for storing information about the display target for each 3DS screen
|
||||
struct ScreenInfo {
|
||||
TextureHandle display_texture;
|
||||
TextureHandle texture;
|
||||
SamplerHandle sampler;
|
||||
Common::Rectangle<f32> display_texcoords;
|
||||
GPU::Regs::PixelFormat format;
|
||||
};
|
||||
|
||||
// Uniform data used for presenting the 3DS screens
|
||||
struct PresentUniformData {
|
||||
glm::mat4 modelview;
|
||||
glm::vec4 i_resolution;
|
||||
glm::vec4 o_resolution;
|
||||
int screen_id = 0;
|
||||
int layer = 0;
|
||||
int reverse_interlaced = 0;
|
||||
|
||||
// Returns an immutable byte view of the uniform data
|
||||
auto AsBytes() const {
|
||||
return std::as_bytes(std::span{this, 1});
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(sizeof(PresentUniformData) < 256, "PresentUniformData must be below 256 bytes!");
|
||||
|
||||
// Vertex structure that the drawn screen rectangles are composed of.
|
||||
struct ScreenRectVertex {
|
||||
ScreenRectVertex() = default;
|
||||
ScreenRectVertex(float x, float y, float u, float v) :
|
||||
position(x, y), tex_coord(u, v) {}
|
||||
|
||||
// Returns the pipeline vertex layout of the vertex
|
||||
constexpr static VertexLayout GetVertexLayout();
|
||||
|
||||
glm::vec2 position;
|
||||
glm::vec2 tex_coord;
|
||||
};
|
||||
|
||||
constexpr u32 PRESENT_PIPELINES = 3;
|
||||
|
||||
class DisplayRenderer {
|
||||
public:
|
||||
DisplayRenderer(Frontend::EmuWindow& window);
|
||||
~DisplayRenderer();
|
||||
|
||||
void SwapBuffers();
|
||||
void TryPresent(int timeout_ms) {}
|
||||
|
||||
float GetCurrentFPS() const {
|
||||
return m_current_fps;
|
||||
}
|
||||
|
||||
int GetCurrentFrame() const {
|
||||
return m_current_frame;
|
||||
}
|
||||
|
||||
Rasterizer* Rasterizer() const {
|
||||
return rasterizer.get();
|
||||
}
|
||||
|
||||
Frontend::EmuWindow& GetRenderWindow() {
|
||||
return render_window;
|
||||
}
|
||||
|
||||
const Frontend::EmuWindow& GetRenderWindow() const {
|
||||
return render_window;
|
||||
}
|
||||
|
||||
void Sync();
|
||||
|
||||
private:
|
||||
void PrepareRendertarget();
|
||||
void ConfigureFramebufferTexture(ScreenInfo& screen, const GPU::Regs::FramebufferConfig& framebuffer);
|
||||
|
||||
// Updates display pipeline according to the shader configuration
|
||||
void ReloadPresentPipeline();
|
||||
|
||||
// Updates the sampler used for special effects
|
||||
void ReloadSampler() {}
|
||||
|
||||
// Draws the emulated screens to the emulator window.
|
||||
void DrawScreens(bool flipped);
|
||||
|
||||
// Draws a single texture to the emulator window, optionally rotating
|
||||
// the texture to correct for the 3DS's LCD rotation.
|
||||
void DrawSingleScreen(u32 screen, bool rotated, float x, float y, float w, float h);
|
||||
|
||||
// Loads framebuffer from emulated memory into the display information structure
|
||||
void LoadFBToScreenInfo(const GPU::Regs::FramebufferConfig& framebuffer,
|
||||
ScreenInfo& screen_info, bool right_eye);
|
||||
// Fills active OpenGL texture with the given RGB color.
|
||||
void LoadColorToActiveTexture(u8 color_r, u8 color_g, u8 color_b, const ScreenInfo& screen);
|
||||
|
||||
// Updates the framebuffer layout of the contained render window handle.
|
||||
void UpdateCurrentFramebufferLayout(bool is_portrait_mode = {});
|
||||
|
||||
private:
|
||||
std::unique_ptr<VideoCore::Rasterizer> rasterizer;
|
||||
std::unique_ptr<BackendBase> backend;
|
||||
Frontend::EmuWindow& render_window;
|
||||
Common::Vec4f clear_color;
|
||||
f32 m_current_fps = 0.0f;
|
||||
int m_current_frame = 0;
|
||||
|
||||
// Present pipelines (Normal, Anaglyph, Interlaced)
|
||||
std::array<PipelineHandle, PRESENT_PIPELINES> present_pipelines;
|
||||
std::array<ShaderHandle, PRESENT_PIPELINES> present_shaders;
|
||||
PipelineHandle current_pipeline;
|
||||
ShaderHandle vertex_shader;
|
||||
|
||||
// Display information for top and bottom screens respectively
|
||||
std::array<ScreenInfo, 3> screen_infos;
|
||||
PresentUniformData uniform_data;
|
||||
BufferHandle vertex_buffer;
|
||||
};
|
||||
|
||||
} // namespace VideoCore
|
@ -312,10 +312,11 @@ uniform int reverse_interlaced;
|
||||
|
||||
void main() {
|
||||
float screen_row = o_resolution.x * frag_tex_coord.x;
|
||||
if (int(screen_row) % 2 == reverse_interlaced)
|
||||
if (int(screen_row) % 2 == reverse_interlaced) {
|
||||
color = texture(color_texture, frag_tex_coord);
|
||||
else
|
||||
} else {
|
||||
color = texture(color_texture_r, frag_tex_coord);
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
|
@ -18,7 +18,7 @@ class Texture;
|
||||
constexpr u32 RENDERPASS_COUNT = (MAX_COLOR_FORMATS + 1) * (MAX_DEPTH_FORMATS + 1);
|
||||
constexpr u32 DESCRIPTOR_BANK_SIZE = 64;
|
||||
|
||||
class Backend : public VideoCore::BackendBase {
|
||||
class Backend final : public VideoCore::BackendBase {
|
||||
public:
|
||||
Backend(Frontend::EmuWindow& window);
|
||||
~Backend();
|
||||
|
@ -8,10 +8,7 @@
|
||||
#include "core/settings.h"
|
||||
#include "video_core/pica.h"
|
||||
#include "video_core/pica_state.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/renderer_opengl/gl_vars.h"
|
||||
#include "video_core/renderer_opengl/renderer_opengl.h"
|
||||
#include "video_core/renderer_vulkan/renderer_vulkan.h"
|
||||
#include "video_core/common/renderer.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -19,7 +16,7 @@
|
||||
|
||||
namespace VideoCore {
|
||||
|
||||
std::unique_ptr<RendererBase> g_renderer; ///< Renderer plugin
|
||||
std::unique_ptr<DisplayRenderer> g_renderer; ///< Renderer plugin
|
||||
|
||||
std::atomic<bool> g_hw_renderer_enabled;
|
||||
std::atomic<bool> g_shader_jit_enabled;
|
||||
@ -44,25 +41,14 @@ ResultStatus Init(Frontend::EmuWindow& emu_window, Memory::MemorySystem& memory)
|
||||
g_memory = &memory;
|
||||
Pica::Init();
|
||||
|
||||
OpenGL::GLES = Settings::values.use_gles;
|
||||
|
||||
g_renderer = std::make_unique<Vulkan::RendererVulkan>(emu_window);
|
||||
ResultStatus result = g_renderer->Init();
|
||||
|
||||
if (result != ResultStatus::Success) {
|
||||
LOG_ERROR(Render, "initialization failed !");
|
||||
} else {
|
||||
LOG_DEBUG(Render, "initialized OK");
|
||||
}
|
||||
|
||||
return result;
|
||||
g_renderer = std::make_unique<DisplayRenderer>(emu_window);
|
||||
return ResultStatus::Success;
|
||||
}
|
||||
|
||||
/// Shutdown the video core
|
||||
void Shutdown() {
|
||||
Pica::Shutdown();
|
||||
|
||||
g_renderer->ShutDown();
|
||||
g_renderer.reset();
|
||||
|
||||
LOG_DEBUG(Render, "shutdown OK");
|
||||
|
@ -23,8 +23,8 @@ class MemorySystem;
|
||||
|
||||
namespace VideoCore {
|
||||
|
||||
class RendererBase;
|
||||
extern std::unique_ptr<RendererBase> g_renderer; ///< Renderer plugin
|
||||
class DisplayRenderer;
|
||||
extern std::unique_ptr<DisplayRenderer> g_renderer; ///< Renderer plugin
|
||||
|
||||
// TODO: Wrap these in a user settings struct along with any other graphics settings (often set from
|
||||
// qt ui)
|
||||
|
Reference in New Issue
Block a user