Refactor Vulkan state and pipeline builders

This commit is contained in:
emufan4568
2022-06-08 00:40:27 +03:00
parent a6fc19d7d9
commit ba6795b6cc
18 changed files with 626 additions and 1039 deletions

View File

@@ -32,6 +32,7 @@
#include "video_core/renderer_vulkan/vk_state.h"
#include "video_core/renderer_vulkan/renderer_vulkan.h"
#include "video_core/renderer_vulkan/vk_task_scheduler.h"
#include "video_core/renderer_vulkan/vk_pipeline_builder.h"
#include "video_core/video_core.h"
// Include these late to avoid polluting previous headers
@@ -129,17 +130,17 @@ std::vector<const char*> RequiredExtensions(Frontend::WindowSystemType window_ty
return extensions;
}
static const char vertex_shader[] = R"(
in vec2 vert_position;
in vec2 vert_tex_coord;
out vec2 frag_tex_coord;
static const char vertex_shader_source[] = R"(
layout (location = 0) in vec2 vert_position;
layout (location = 1) in vec2 vert_tex_coord;
layout (location = 0) out vec2 frag_tex_coord;
// This is a truncated 3x3 matrix for 2D transformations:
// The upper-left 2x2 submatrix performs scaling/rotation/mirroring.
// The third column performs translation.
// The third row could be used for projection, which we don't need in 2D. It hence is assumed to
// implicitly be [0, 0, 1]
uniform mat3x2 modelview_matrix;
layout (push_constant) uniform mat3x2 modelview_matrix;
void main() {
// Multiply input position by the rotscale part of the matrix and then manually translate by
@@ -150,13 +151,15 @@ void main() {
}
)";
static const char fragment_shader[] = R"(
in vec2 frag_tex_coord;
layout(location = 0) out vec4 color;
static const char fragment_shader_source[] = R"(
layout (location = 0) in vec2 frag_tex_coord;
layout (location = 0) out vec4 color;
uniform vec4 i_resolution;
uniform vec4 o_resolution;
uniform int layer;
layout (push_constant) uniform DrawInfo {
vec4 i_resolution;
vec4 o_resolution;
int layer;
};
uniform sampler2D color_texture;
@@ -165,70 +168,32 @@ void main() {
}
)";
static const char fragment_shader_anaglyph[] = 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);
in vec2 frag_tex_coord;
out vec4 color;
uniform vec4 resolution;
uniform int layer;
uniform sampler2D color_texture;
uniform sampler2D color_texture_r;
void main() {
vec4 color_tex_l = texture(color_texture, frag_tex_coord);
vec4 color_tex_r = texture(color_texture_r, frag_tex_coord);
color = vec4(color_tex_l.rgb*l+color_tex_r.rgb*r, color_tex_l.a);
}
)";
static const char fragment_shader_interlaced[] = R"(
in vec2 frag_tex_coord;
out vec4 color;
uniform vec4 o_resolution;
uniform sampler2D color_texture;
uniform sampler2D color_texture_r;
uniform int reverse_interlaced;
void main() {
float screen_row = o_resolution.x * frag_tex_coord.x;
if (int(screen_row) % 2 == reverse_interlaced)
color = texture(color_texture, frag_tex_coord);
else
color = texture(color_texture_r, frag_tex_coord);
}
)";
/**
* Vertex structure that the drawn screen rectangles are composed of.
*/
struct ScreenRectVertex {
ScreenRectVertex(GLfloat x, GLfloat y, GLfloat u, GLfloat v) {
position[0] = x;
position[1] = y;
tex_coord[0] = u;
tex_coord[1] = v;
struct ScreenRectVertexBase {
ScreenRectVertexBase() = default;
ScreenRectVertexBase(float x, float y, float u, float v) {
position.x = x;
position.y = y;
tex_coord.x = u;
tex_coord.y = v;
}
GLfloat position[2];
GLfloat tex_coord[2];
glm::vec2 position;
glm::vec2 tex_coord;
};
struct ScreenRectVertex : public ScreenRectVertexBase {
ScreenRectVertex() = default;
ScreenRectVertex(float x, float y, float u, float v) : ScreenRectVertexBase(x, y, u, v) {};
static constexpr auto binding_desc = vk::VertexInputBindingDescription(0, sizeof(ScreenRectVertexBase));
static constexpr std::array<vk::VertexInputAttributeDescription, 8> attribute_desc =
{
vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32Sfloat, offsetof(ScreenRectVertexBase, position)),
vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32Sfloat, offsetof(ScreenRectVertexBase, tex_coord)),
};
};
/**
@@ -273,14 +238,10 @@ MICROPROFILE_DEFINE(OpenGL_WaitPresent, "OpenGL", "Wait For Present", MP_RGB(128
/// Swap buffers (render frame)
void RendererVulkan::SwapBuffers() {
// Maintain the rasterizer's state as a priority
OpenGLState prev_state = OpenGLState::GetCurState();
state.Apply();
PrepareRendertarget();
const auto& layout = render_window.GetFramebufferLayout();
RenderToMailbox(layout, render_window.mailbox, false);
DrawScreens(layout, false);
m_current_frame++;
@@ -292,7 +253,6 @@ void RendererVulkan::SwapBuffers() {
Core::System::GetInstance().CoreTiming().GetGlobalTimeUs());
Core::System::GetInstance().perf_stats->BeginSystemFrame();
prev_state.Apply();
RefreshRasterizerSetting();
if (Pica::g_debug_context && Pica::g_debug_context->recorder) {
@@ -315,9 +275,9 @@ void RendererVulkan::PrepareRendertarget() {
if (color_fill.is_enabled) {
LoadColorToActiveGLTexture(color_fill.color_r, color_fill.color_g, color_fill.color_b, screen_infos[i]);
} else {
auto rect = screen_infos[i].texture->GetExtent();
auto extent = screen_infos[i].texture.GetArea().extent;
auto format = screen_infos[i].format;
if (rect.width != framebuffer.width || rect.height != framebuffer.height ||
if (extent.width != framebuffer.width || extent.height != framebuffer.height ||
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
@@ -328,40 +288,12 @@ void RendererVulkan::PrepareRendertarget() {
LoadFBToScreenInfo(framebuffer, screen_infos[i], i == 1);
// Resize the texture in case the framebuffer size has changed
screen_infos[i].texture.width = framebuffer.width;
screen_infos[i].texture.height = framebuffer.height;
//screen_infos[i].texture.width = framebuffer.width;
//screen_infos[i].texture.height = framebuffer.height;
}
}
}
void RendererVulkan::RenderToMailbox(const Layout::FramebufferLayout& layout,
std::unique_ptr<Frontend::TextureMailbox>& mailbox,
bool flipped) {
Frontend::Frame* frame;
{
MICROPROFILE_SCOPE(OpenGL_WaitPresent);
frame = mailbox->GetRenderFrame();
}
{
MICROPROFILE_SCOPE(OpenGL_RenderFrame);
// Recreate the frame if the size of the window has changed
if (layout.width != frame->width || layout.height != frame->height) {
LOG_DEBUG(Render_OpenGL, "Reloading render frame");
mailbox->ReloadRenderFrame(frame, layout.width, layout.height);
}
GLuint render_texture = frame->color.handle;
state.draw.draw_framebuffer = frame->render.handle;
state.Apply();
DrawScreens(layout, flipped);
// Create a fence for the frontend to wait on and swap this frame to OffTex
mailbox->ReleaseRenderFrame(frame);
}
}
/**
* Loads framebuffer from emulated memory into the active OpenGL texture.
*/
@@ -392,16 +324,16 @@ void RendererVulkan::LoadFBToScreenInfo(const GPU::Regs::FramebufferConfig& fram
if (!Rasterizer()->AccelerateDisplay(framebuffer, framebuffer_addr, static_cast<u32>(pixel_stride), screen_info)) {
// Reset the screen info's display texture to its own permanent texture
screen_info.display_texture = screen_info.texture;
screen_info.display_texture = screen_info.texture.GetHandle();
screen_info.display_texcoords = Common::Rectangle<float>(0.f, 0.f, 1.f, 1.f);
Memory::RasterizerFlushRegion(framebuffer_addr, framebuffer.stride * framebuffer.height);
vk::Rect2D region{{0, 0}, {framebuffer.width, framebuffer.height}};
std::span<u8> framebuffer_data(VideoCore::g_memory->GetPhysicalPointer(framebuffer_addr),
screen_info.texture->GetSize());
screen_info.texture.GetSize());
screen_info.texture->Upload(0, 1, pixel_stride, region, framebuffer_data);
screen_info.texture.Upload(0, 1, pixel_stride, region, framebuffer_data);
}
}
@@ -409,9 +341,8 @@ void RendererVulkan::LoadFBToScreenInfo(const GPU::Regs::FramebufferConfig& fram
* Fills active OpenGL texture with the given RGB color. Since the color is solid, the texture can
* be 1x1 but will stretch across whatever it's rendered on.
*/
void RendererVulkan::LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b,
const ScreenInfo& screen) {
state.texture_units[0].texture_2d = texture.resource.handle;
void RendererVulkan::LoadColorToActiveGLTexture(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);
@@ -421,161 +352,105 @@ void RendererVulkan::LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color
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();
state.Apply();*/
}
/**
* Initializes the OpenGL state and creates persistent objects.
*/
void RendererVulkan::InitOpenGLObjects() {
void RendererVulkan::CreateVulkanObjects() {
glClearColor(Settings::values.bg_red, Settings::values.bg_green, Settings::values.bg_blue,
0.0f);
filter_sampler.Create();
ReloadSampler();
//filter_sampler.Create();
//ReloadSampler();
ReloadShader();
// Generate VBO handle for drawing
vertex_buffer.Create();
VKBuffer::Info vertex_info{
.size = sizeof(ScreenRectVertex) * 4,
.properties = vk::MemoryPropertyFlagBits::eDeviceLocal,
.usage = vk::BufferUsageFlagBits::eVertexBuffer |
vk::BufferUsageFlagBits::eTransferDst
};
vertex_buffer.Create(vertex_info);
}
// Generate VAO
vertex_array.Create();
void RendererVulkan::ConfigureRenderPipeline() {
// Define the descriptor sets we will be using
vk::DescriptorSetLayoutBinding color_texture{
0, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment
};
vk::DescriptorSetLayoutCreateInfo color_texture_info{{}, color_texture};
state.draw.vertex_array = vertex_array.handle;
state.draw.vertex_buffer = vertex_buffer.handle;
state.draw.uniform_buffer = 0;
state.Apply();
auto& device = g_vk_instace->GetDevice();
descriptor_layout = device.createDescriptorSetLayoutUnique(color_texture_info);
// Attach vertex data to VAO
glBufferData(GL_ARRAY_BUFFER, sizeof(ScreenRectVertex) * 4, nullptr, GL_STREAM_DRAW);
glVertexAttribPointer(attrib_position, 2, GL_FLOAT, GL_FALSE, sizeof(ScreenRectVertex),
(GLvoid*)offsetof(ScreenRectVertex, position));
glVertexAttribPointer(attrib_tex_coord, 2, GL_FLOAT, GL_FALSE, sizeof(ScreenRectVertex),
(GLvoid*)offsetof(ScreenRectVertex, tex_coord));
glEnableVertexAttribArray(attrib_position);
glEnableVertexAttribArray(attrib_tex_coord);
// Build the display pipeline layout
PipelineLayoutBuilder lbuilder;
lbuilder.AddDescriptorSet(descriptor_layout.get());
lbuilder.AddPushConstants(vk::ShaderStageFlagBits::eVertex, 0, sizeof(glm::mat2x3));
lbuilder.AddPushConstants(vk::ShaderStageFlagBits::eFragment, 0, sizeof(DrawInfo));
pipeline_layout = vk::UniquePipelineLayout{lbuilder.Build()};
// Allocate textures for each screen
for (auto& screen_info : screen_infos) {
screen_info.texture.resource.Create();
std::array<vk::DynamicState, 3> dynamic_states{
vk::DynamicState::eLineWidth, vk::DynamicState::eViewport, vk::DynamicState::eScissor,
};
// Allocation of storage is deferred until the first frame, when we
// know the framebuffer size.
// Build the display pipeline
PipelineBuilder builder;
builder.SetNoStencilState();
builder.SetNoBlendingState();
builder.SetNoDepthTestState();
builder.SetNoCullRasterizationState();
builder.SetLineWidth(1.0f);
builder.SetPrimitiveTopology(vk::PrimitiveTopology::eTriangleList);
builder.SetShaderStage(vk::ShaderStageFlagBits::eVertex, vertex_shader.get());
builder.SetShaderStage(vk::ShaderStageFlagBits::eFragment, fragment_shader.get());
builder.SetDynamicStates(dynamic_states);
builder.SetPipelineLayout(pipeline_layout.get());
state.texture_units[0].texture_2d = screen_info.texture.resource.handle;
state.Apply();
// Configure vertex buffer
auto attributes = ScreenRectVertex::attribute_desc;
builder.AddVertexBuffer(0, sizeof(ScreenRectVertex), vk::VertexInputRate::eVertex, attributes);
glActiveTexture(GL_TEXTURE0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
screen_info.display_texture = screen_info.texture.resource.handle;
}
state.texture_units[0].texture_2d = 0;
state.Apply();
pipeline = vk::UniquePipeline{builder.Build()};
}
void RendererVulkan::ReloadSampler() {
glSamplerParameteri(filter_sampler.handle, GL_TEXTURE_MIN_FILTER,
/*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);
glSamplerParameteri(filter_sampler.handle, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);*/
}
void RendererVulkan::ReloadShader() {
// Link shaders and get variable locations
std::string shader_data = fragment_shader;
/*if (GLES) {
shader_data += fragment_shader_precision_OES;
}
if (Settings::values.render_3d == Settings::StereoRenderOption::Anaglyph) {
if (Settings::values.pp_shader_name == "dubois (builtin)") {
shader_data += fragment_shader_anaglyph;
} else {
std::string shader_text =
OpenGL::GetPostProcessingShaderCode(true, Settings::values.pp_shader_name);
if (shader_text.empty()) {
// Should probably provide some information that the shader couldn't load
shader_data += fragment_shader_anaglyph;
} else {
shader_data += shader_text;
}
}
} else if (Settings::values.render_3d == Settings::StereoRenderOption::Interlaced ||
Settings::values.render_3d == Settings::StereoRenderOption::ReverseInterlaced) {
if (Settings::values.pp_shader_name == "horizontal (builtin)") {
shader_data += fragment_shader_interlaced;
} else {
std::string shader_text =
OpenGL::GetPostProcessingShaderCode(true, Settings::values.pp_shader_name);
if (shader_text.empty()) {
// Should probably provide some information that the shader couldn't load
shader_data += fragment_shader_interlaced;
} else {
shader_data += shader_text;
}
}
} else {
if (Settings::values.pp_shader_name == "none (builtin)") {
shader_data += fragment_shader;
} else {
std::string shader_text =
OpenGL::GetPostProcessingShaderCode(false, Settings::values.pp_shader_name);
if (shader_text.empty()) {
// Should probably provide some information that the shader couldn't load
shader_data += fragment_shader;
} else {
shader_data += shader_text;
}
}
}*/
shader.Create(vertex_shader, shader_data.c_str());
state.draw.shader_program = shader.handle;
state.Apply();
uniform_modelview_matrix = glGetUniformLocation(shader.handle, "modelview_matrix");
uniform_color_texture = glGetUniformLocation(shader.handle, "color_texture");
if (Settings::values.render_3d == Settings::StereoRenderOption::Anaglyph ||
Settings::values.render_3d == Settings::StereoRenderOption::Interlaced ||
Settings::values.render_3d == Settings::StereoRenderOption::ReverseInterlaced) {
uniform_color_texture_r = glGetUniformLocation(shader.handle, "color_texture_r");
}
if (Settings::values.render_3d == Settings::StereoRenderOption::Interlaced ||
Settings::values.render_3d == Settings::StereoRenderOption::ReverseInterlaced) {
GLuint uniform_reverse_interlaced =
glGetUniformLocation(shader.handle, "reverse_interlaced");
if (Settings::values.render_3d == Settings::StereoRenderOption::ReverseInterlaced)
glUniform1i(uniform_reverse_interlaced, 1);
else
glUniform1i(uniform_reverse_interlaced, 0);
}
uniform_i_resolution = glGetUniformLocation(shader.handle, "i_resolution");
uniform_o_resolution = glGetUniformLocation(shader.handle, "o_resolution");
uniform_layer = glGetUniformLocation(shader.handle, "layer");
attrib_position = glGetAttribLocation(shader.handle, "vert_position");
attrib_tex_coord = glGetAttribLocation(shader.handle, "vert_tex_coord");
vertex_shader = vk::UniqueShaderModule{VulkanState::CompileShader(vertex_shader_source,
vk::ShaderStageFlagBits::eVertex)};
fragment_shader = vk::UniqueShaderModule{VulkanState::CompileShader(fragment_shader_source,
vk::ShaderStageFlagBits::eFragment)};
}
void RendererVulkan::ConfigureFramebufferTexture(TextureInfo& texture,
const GPU::Regs::FramebufferConfig& framebuffer) {
void RendererVulkan::ConfigureFramebufferTexture(ScreenInfo& screen, const GPU::Regs::FramebufferConfig& framebuffer) {
GPU::Regs::PixelFormat format = framebuffer.color_format;
GLint internal_format;
texture.format = format;
texture.width = framebuffer.width;
texture.height = framebuffer.height;
VKTexture::Info texture_info{
.width = framebuffer.width,
.height = framebuffer.height,
.type = vk::ImageType::e2D,
.view_type = vk::ImageViewType::e2D,
.usage = vk::ImageUsageFlagBits::eColorAttachment |
vk::ImageUsageFlagBits::eTransferDst,
.aspect = vk::ImageAspectFlagBits::eColor,
};
switch (format) {
case GPU::Regs::PixelFormat::RGBA8:
internal_format = GL_RGBA;
texture.gl_format = GL_RGBA;
texture.gl_type = GLES ? GL_UNSIGNED_BYTE : GL_UNSIGNED_INT_8_8_8_8;
texture_info.format = vk::Format::eR8G8B8A8Unorm;
break;
case GPU::Regs::PixelFormat::RGB8:
@@ -583,44 +458,33 @@ void RendererVulkan::ConfigureFramebufferTexture(TextureInfo& texture,
// specific OpenGL type used in this function using native-endian (that is, little-endian
// mostly everywhere) for words or half-words.
// TODO: check how those behave on big-endian processors.
internal_format = GL_RGB;
//internal_format = GL_RGB;
// GLES Dosen't support BGR , Use RGB instead
texture.gl_format = GLES ? GL_RGB : GL_BGR;
texture.gl_type = GL_UNSIGNED_BYTE;
//texture.gl_format = GLES ? GL_RGB : GL_BGR;
//texture.gl_type = GL_UNSIGNED_BYTE;
texture_info.format = vk::Format::eR8G8B8Unorm;
break;
case GPU::Regs::PixelFormat::RGB565:
internal_format = GL_RGB;
texture.gl_format = GL_RGB;
texture.gl_type = GL_UNSIGNED_SHORT_5_6_5;
texture_info.format = vk::Format::eR5G6B5UnormPack16;
break;
case GPU::Regs::PixelFormat::RGB5A1:
internal_format = GL_RGBA;
texture.gl_format = GL_RGBA;
texture.gl_type = GL_UNSIGNED_SHORT_5_5_5_1;
texture_info.format = vk::Format::eR5G5B5A1UnormPack16;
break;
case GPU::Regs::PixelFormat::RGBA4:
internal_format = GL_RGBA;
texture.gl_format = GL_RGBA;
texture.gl_type = GL_UNSIGNED_SHORT_4_4_4_4;
texture_info.format = vk::Format::eR4G4B4A4UnormPack16;
break;
default:
UNIMPLEMENTED();
}
state.texture_units[0].texture_2d = texture.resource.handle;
state.Apply();
glActiveTexture(GL_TEXTURE0);
glTexImage2D(GL_TEXTURE_2D, 0, internal_format, texture.width, texture.height, 0,
texture.gl_format, texture.gl_type, nullptr);
state.texture_units[0].texture_2d = 0;
state.Apply();
auto& texture = screen.texture;
texture.Destroy();
texture.Create(texture_info);
}
/**
@@ -638,15 +502,21 @@ void RendererVulkan::DrawSingleScreenRotated(const ScreenInfo& screen_info, floa
ScreenRectVertex(x + w, y + h, texcoords.top, texcoords.right),
}};
auto command_buffer = g_vk_task_scheduler->GetCommandBuffer();
// 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();
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);
auto [width, height] = screen_info.texture.GetArea().extent;
draw_info.i_resolution = glm::vec4(width * scale_factor, height * scale_factor,
1.0f / (width * scale_factor),
1.0f / (height * scale_factor));
draw_info.o_resolution = glm::vec4(h, w, 1.0f / h, 1.0f / w);
command_buffer.pushConstants(pipeline_layout.get(), vk::ShaderStageFlagBits::eFragment,
0, sizeof(DrawInfo), &draw_info);
state.texture_units[0].texture_2d = screen_info.display_texture;
state.texture_units[0].sampler = filter_sampler.handle;
state.Apply();
@@ -776,12 +646,12 @@ void RendererVulkan::DrawScreens(const Layout::FramebufferLayout& layout, bool f
ReloadSampler();
}
if (VideoCore::g_renderer_shader_update_requested.exchange(false)) {
/*if (VideoCore::g_renderer_shader_update_requested.exchange(false)) {
// Update fragment shader before drawing
shader.Release();
// Link shaders and get variable locations
ReloadShader();
}
}*/
const auto& top_screen = layout.top_screen;
const auto& bottom_screen = layout.bottom_screen;
@@ -930,105 +800,42 @@ void RendererVulkan::DrawScreens(const Layout::FramebufferLayout& layout, bool f
}
}
void RendererVulkan::TryPresent(int timeout_ms) {
bool RendererVulkan::BeginPresent() {
// Previous frame needs to be presented before we can acquire the swap chain.
g_vulkan_context->WaitForPresentComplete();
g_vk_task_scheduler->Submit(true);
auto available = swapchain->AcquireNextImage();
auto cmdbuffer = g_vk_task_scheduler->GetCommandBuffer();
const auto& layout = render_window.GetFramebufferLayout();
auto frame = render_window.mailbox->TryGetPresentFrame(timeout_ms);
if (!frame) {
LOG_DEBUG(Render_Vulkan, "TryGetPresentFrame returned no frame to present");
return;
}
// Swap chain images start in undefined
Vulkan::Texture& swap_chain_texture = m_swap_chain->GetCurrentTexture();
swap_chain_texture.OverrideImageLayout(VK_IMAGE_LAYOUT_UNDEFINED);
swap_chain_texture.TransitionToLayout(cmdbuffer, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
// Clearing before a full overwrite of a fbo can signal to drivers that they can avoid a
// readback since we won't be doing any blending
glClear(GL_COLOR_BUFFER_BIT);
const VkClearValue clear_value = {{{0.0f, 0.0f, 0.0f, 1.0f}}};
const VkRenderPassBeginInfo rp = {VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, nullptr, m_swap_chain->GetClearRenderPass(),
m_swap_chain->GetCurrentFramebuffer(), {{0, 0}, {swap_chain_texture.GetWidth(), swap_chain_texture.GetHeight()}}, 1u, &clear_value};
vkCmdBeginRenderPass(g_vulkan_context->GetCurrentCommandBuffer(), &rp, VK_SUBPASS_CONTENTS_INLINE);
// Recreate the presentation FBO if the color attachment was changed
if (frame->color_reloaded) {
LOG_DEBUG(Render_Vulkan, "Reloading present frame");
render_window.mailbox->ReloadPresentFrame(frame, layout.width, layout.height);
}
glWaitSync(frame->render_fence, 0, GL_TIMEOUT_IGNORED);
// INTEL workaround.
// Normally we could just delete the draw fence here, but due to driver bugs, we can just delete
// it on the emulation thread without too much penalty
// glDeleteSync(frame.render_sync);
// frame.render_sync = 0;
glBindFramebuffer(GL_READ_FRAMEBUFFER, frame->present.handle);
glBlitFramebuffer(0, 0, frame->width, frame->height, 0, 0, layout.width, layout.height,
GL_COLOR_BUFFER_BIT, GL_LINEAR);
// Delete the fence if we're re-presenting to avoid leaking fences
if (frame->present_fence) {
glDeleteSync(frame->present_fence);
}
/* insert fence for the main thread to block on */
frame->present_fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
glFlush();
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
const VkViewport vp{
0.0f, 0.0f, static_cast<float>(swap_chain_texture.GetWidth()), static_cast<float>(swap_chain_texture.GetHeight()), 0.0f, 1.0f};
const VkRect2D scissor{{0, 0}, {static_cast<u32>(swap_chain_texture.GetWidth()), static_cast<u32>(swap_chain_texture.GetHeight())}};
vkCmdSetViewport(g_vulkan_context->GetCurrentCommandBuffer(), 0, 1, &vp);
vkCmdSetScissor(g_vulkan_context->GetCurrentCommandBuffer(), 0, 1, &scissor);
return true;
}
/// Updates the framerate
void RendererVulkan::UpdateFramerate() {}
void RendererVulkan::EndPresent() {
ImGui::Render();
ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData());
static const char* GetSource(GLenum source) {
#define RET(s) \
case GL_DEBUG_SOURCE_##s: \
return #s
switch (source) {
RET(API);
RET(WINDOW_SYSTEM);
RET(SHADER_COMPILER);
RET(THIRD_PARTY);
RET(APPLICATION);
RET(OTHER);
default:
UNREACHABLE();
}
#undef RET
}
VkCommandBuffer cmdbuffer = g_vulkan_context->GetCurrentCommandBuffer();
vkCmdEndRenderPass(g_vulkan_context->GetCurrentCommandBuffer());
m_swap_chain->GetCurrentTexture().TransitionToLayout(cmdbuffer, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
static const char* GetType(GLenum type) {
#define RET(t) \
case GL_DEBUG_TYPE_##t: \
return #t
switch (type) {
RET(ERROR);
RET(DEPRECATED_BEHAVIOR);
RET(UNDEFINED_BEHAVIOR);
RET(PORTABILITY);
RET(PERFORMANCE);
RET(OTHER);
RET(MARKER);
default:
UNREACHABLE();
}
#undef RET
}
static void APIENTRY DebugHandler(GLenum source, GLenum type, GLuint id, GLenum severity,
GLsizei length, const GLchar* message, const void* user_param) {
Log::Level level;
switch (severity) {
case GL_DEBUG_SEVERITY_HIGH:
level = Log::Level::Critical;
break;
case GL_DEBUG_SEVERITY_MEDIUM:
level = Log::Level::Warning;
break;
case GL_DEBUG_SEVERITY_NOTIFICATION:
case GL_DEBUG_SEVERITY_LOW:
level = Log::Level::Debug;
break;
}
LOG_GENERIC(Log::Class::Render_OpenGL, level, "{} {} {}: {}", GetSource(source), GetType(type),
id, message);
g_vulkan_context->SubmitCommandBuffer(m_swap_chain->GetImageAvailableSemaphore(), m_swap_chain->GetRenderingFinishedSemaphore(),
m_swap_chain->GetSwapChain(), m_swap_chain->GetCurrentImageIndex(), !m_swap_chain->IsPresentModeSynchronizing());
g_vulkan_context->MoveToNextCommandBuffer();
}
/// Initialize the renderer
@@ -1050,14 +857,19 @@ VideoCore::ResultStatus RendererVulkan::Init() {
g_vk_instace = std::make_unique<VKInstance>();
g_vk_instace->Create(instance, physical_device, surface, true);
// Create Vulkan state and task manager
VulkanState::Create();
g_vk_task_scheduler = std::make_unique<VKTaskScheduler>();
g_vk_task_scheduler->Create();
auto& telemetry_session = Core::System::GetInstance().TelemetrySession();
constexpr auto user_system = Common::Telemetry::FieldType::UserSystem;
telemetry_session.AddField(user_system, "GPU_Vendor", "NVIDIA");
telemetry_session.AddField(user_system, "GPU_Model", "GTX 1650");
telemetry_session.AddField(user_system, "GPU_Vulkan_Version", "Vulkan 1.3");
InitOpenGLObjects();
// Initialize the renderer
CreateVulkanObjects();
RefreshRasterizerSetting();
return VideoCore::ResultStatus::Success;

View File

@@ -30,12 +30,18 @@ namespace Vulkan {
/// Structure used for storing information about the display target for each 3DS screen
struct ScreenInfo {
Vulkan::VKTexture* display_texture;
vk::Image display_texture;
Common::Rectangle<float> display_texcoords;
Vulkan::VKTexture* texture;
Vulkan::VKTexture texture;
GPU::Regs::PixelFormat format;
};
struct DrawInfo {
glm::vec4 i_resolution;
glm::vec4 o_resolution;
int layer;
};
class RendererVulkan : public RendererBase {
public:
RendererVulkan(Frontend::EmuWindow& window);
@@ -47,29 +53,25 @@ public:
/// Shutdown the renderer
void ShutDown() override;
/// Finalizes rendering the guest frame
void SwapBuffers() override;
/// Draws the latest frame from texture mailbox to the currently bound draw framebuffer in this
/// context
void TryPresent(int timeout_ms) override;
bool BeginPresent();
void EndPresent();
private:
void InitOpenGLObjects();
void CreateVulkanObjects();
void ConfigureRenderPipeline();
void ReloadSampler();
void ReloadShader();
void PrepareRendertarget();
void RenderToMailbox(const Layout::FramebufferLayout& layout,
std::unique_ptr<Frontend::TextureMailbox>& mailbox, bool flipped);
void ConfigureFramebufferTexture(ScreenInfo& screen, const GPU::Regs::FramebufferConfig& framebuffer);
void DrawScreens(const Layout::FramebufferLayout& layout, bool flipped);
void DrawSingleScreenRotated(const ScreenInfo& screen_info, float x, float y, float w, float h);
void DrawSingleScreen(const ScreenInfo& screen_info, float x, float y, float w, float h);
void DrawSingleScreenStereoRotated(const ScreenInfo& screen_info_l,
const ScreenInfo& screen_info_r, float x, float y, float w,
float h);
const ScreenInfo& screen_info_r, float x, float y, float w, float h);
void DrawSingleScreenStereo(const ScreenInfo& screen_info_l, const ScreenInfo& screen_info_r,
float x, float y, float w, float h);
void UpdateFramerate();
// Loads framebuffer from emulated memory into the display information structure
@@ -78,12 +80,18 @@ private:
// Fills active OpenGL texture with the given RGB color.
void LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b, const ScreenInfo& screen);
private:
// Vulkan state
DrawInfo draw_info{};
VulkanState state;
// OpenGL object IDs
// Vulkan objects
vk::UniqueShaderModule vertex_shader, fragment_shader;
vk::UniqueDescriptorSet descriptor_set;
vk::UniqueDescriptorSetLayout descriptor_layout;
vk::UniquePipelineLayout pipeline_layout;
vk::UniquePipeline pipeline;
VKBuffer vertex_buffer;
//OGLProgram shader;
//OGLSampler filter_sampler;
/// Display information for top and bottom screens respectively
std::array<ScreenInfo, 3> screen_infos;

View File

@@ -12,19 +12,7 @@
namespace Vulkan {
VKBuffer::~VKBuffer() {
if (buffer) {
if (memory != nullptr) {
g_vk_instace->GetDevice().unmapMemory(buffer_memory);
}
auto deleter = [this]() {
auto& device = g_vk_instace->GetDevice();
device.destroyBuffer(buffer);
device.freeMemory(buffer_memory);
};
g_vk_task_scheduler->Schedule(deleter);
}
Destroy();
}
void VKBuffer::Create(const VKBuffer::Info& info) {
@@ -46,6 +34,32 @@ void VKBuffer::Create(const VKBuffer::Info& info) {
if (info.properties & vk::MemoryPropertyFlagBits::eHostVisible) {
memory = device.mapMemory(buffer_memory, 0, info.size);
}
for (auto& format : info.view_formats) {
if (format != vk::Format::eUndefined) {
views[view_count++] = device.createBufferView({{}, buffer, format, 0, info.size});
}
}
}
void VKBuffer::Destroy() {
if (buffer) {
if (memory != nullptr) {
g_vk_instace->GetDevice().unmapMemory(buffer_memory);
}
auto deleter = [this]() {
auto& device = g_vk_instace->GetDevice();
device.destroyBuffer(buffer);
device.freeMemory(buffer_memory);
for (int i = 0; i < view_count; i++) {
device.destroyBufferView(views[i]);
}
};
g_vk_task_scheduler->Schedule(deleter);
}
}
void VKBuffer::CopyBuffer(VKBuffer* src_buffer, VKBuffer* dst_buffer, vk::BufferCopy region) {

View File

@@ -7,11 +7,14 @@
#include <memory>
#include <vector>
#include <deque>
#include <span>
#include <vulkan/vulkan.hpp>
#include "common/common_types.h"
namespace Vulkan {
constexpr u32 MAX_BUFFER_VIEWS = 5;
/// Generic Vulkan buffer object used by almost every resource
class VKBuffer final : public NonCopyable {
public:
@@ -19,22 +22,27 @@ public:
u32 size;
vk::MemoryPropertyFlags properties;
vk::BufferUsageFlags usage;
std::array<vk::Format, MAX_BUFFER_VIEWS> view_formats{};
};
VKBuffer() = default;
VKBuffer(VKBuffer&&) = default;
~VKBuffer();
VKBuffer(VKBuffer&&) = default;
VKBuffer& operator=(VKBuffer&&) = default;
/// Create a new Vulkan buffer object
void Create(const Info& info);
void Destroy();
/// Global utility functions used by other objects
static u32 FindMemoryType(u32 type_filter, vk::MemoryPropertyFlags properties);
static void CopyBuffer(VKBuffer* src_buffer, VKBuffer* dst_buffer, vk::BufferCopy region);
/// Return a pointer to the mapped memory if the buffer is host mapped
u8* GetHostPointer() { return reinterpret_cast<u8*>(memory); }
vk::Buffer& GetBuffer() { return buffer; }
u8* GetHostPointer() const { return reinterpret_cast<u8*>(memory); }
const vk::BufferView& GetView(u32 i = 0) const { return views[i]; }
vk::Buffer GetBuffer() const { return buffer; }
u32 GetSize() const { return buffer_info.size; }
private:
@@ -42,6 +50,8 @@ private:
void* memory = nullptr;
vk::Buffer buffer;
vk::DeviceMemory buffer_memory;
u32 view_count = 0;
std::array<vk::BufferView, MAX_BUFFER_VIEWS> views;
};
}

View File

@@ -1,4 +1,4 @@
// Copyright 2020 Citra Emulator Project
// Copyright 2022 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -12,386 +12,12 @@ namespace Vulkan {
using PixelFormat = SurfaceParams::PixelFormat;
class RGBA4toRGB5A1 final : public FormatReinterpreterBase {
public:
RGBA4toRGB5A1() {
constexpr std::string_view vs_source = R"(
#version 450
#extension GL_ARB_separate_shader_objects : enable
layout(location = 0) out vec2 dst_coord;
uniform mediump ivec2 dst_size;
const vec2 vertices[4] =
vec2[4](vec2(-1.0, -1.0), vec2(1.0, -1.0), vec2(-1.0, 1.0), vec2(1.0, 1.0));
void main() {
gl_Position = vec4(vertices[gl_VertexID], 0.0, 1.0);
dst_coord = (vertices[gl_VertexID] / 2.0 + 0.5) * vec2(dst_size);
}
)";
constexpr std::string_view fs_source = R"(
#version 450
#extension GL_ARB_separate_shader_objects : enable
layout (location = 0) in mediump vec2 dst_coord;
layout (location = 0) out lowp vec4 frag_color;
uniform lowp sampler2D source;
uniform mediump ivec2 dst_size;
uniform mediump ivec2 src_size;
uniform mediump ivec2 src_offset;
void main() {
mediump ivec2 tex_coord;
if (src_size == dst_size) {
tex_coord = ivec2(dst_coord);
} else {
highp int tex_index = int(dst_coord.y) * dst_size.x + int(dst_coord.x);
mediump int y = tex_index / src_size.x;
tex_coord = ivec2(tex_index - y * src_size.x, y);
}
tex_coord -= src_offset;
lowp ivec4 rgba4 = ivec4(texelFetch(source, tex_coord, 0) * (exp2(4.0) - 1.0));
lowp ivec3 rgb5 =
((rgba4.rgb << ivec3(1, 2, 3)) | (rgba4.gba >> ivec3(3, 2, 1))) & 0x1F;
frag_color = vec4(vec3(rgb5) / (exp2(5.0) - 1.0), rgba4.a & 0x01);
}
)";
program.Create(vs_source.data(), fs_source.data());
dst_size_loc = glGetUniformLocation(program.handle, "dst_size");
src_size_loc = glGetUniformLocation(program.handle, "src_size");
src_offset_loc = glGetUniformLocation(program.handle, "src_offset");
vao.Create();
}
void Reinterpret(Surface src_surface, const Common::Rectangle<u32>& src_rect,
Surface dst_surface, const Common::Rectangle<u32>& dst_rect) override {
OpenGLState prev_state = OpenGLState::GetCurState();
SCOPE_EXIT({ prev_state.Apply(); });
OpenGLState state;
state.texture_units[0].texture_2d = src_tex;
state.draw.draw_framebuffer = draw_fb_handle;
state.draw.shader_program = program.handle;
state.draw.vertex_array = vao.handle;
state.viewport = {static_cast<GLint>(dst_rect.left), static_cast<GLint>(dst_rect.bottom),
static_cast<GLsizei>(dst_rect.GetWidth()),
static_cast<GLsizei>(dst_rect.GetHeight())};
state.Apply();
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst_tex,
0);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0,
0);
glUniform2i(dst_size_loc, dst_rect.GetWidth(), dst_rect.GetHeight());
glUniform2i(src_size_loc, src_rect.GetWidth(), src_rect.GetHeight());
glUniform2i(src_offset_loc, src_rect.left, src_rect.bottom);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
private:
OGLProgram program;
GLint dst_size_loc{-1}, src_size_loc{-1}, src_offset_loc{-1};
OGLVertexArray vao;
};
class PixelBufferD24S8toABGR final : public FormatReinterpreterBase {
public:
PixelBufferD24S8toABGR() {
attributeless_vao.Create();
d24s8_abgr_buffer.Create();
d24s8_abgr_buffer_size = 0;
constexpr std::string_view vs_source = R"(
const vec2 vertices[4] = vec2[4](vec2(-1.0, -1.0), vec2(1.0, -1.0),
vec2(-1.0, 1.0), vec2(1.0, 1.0));
void main() {
gl_Position = vec4(vertices[gl_VertexID], 0.0, 1.0);
}
)";
std::string fs_source = GLES ? fragment_shader_precision_OES : "";
fs_source += R"(
uniform samplerBuffer tbo;
uniform vec2 tbo_size;
uniform vec4 viewport;
out vec4 color;
void main() {
vec2 tbo_coord = (gl_FragCoord.xy - viewport.xy) * tbo_size / viewport.zw;
int tbo_offset = int(tbo_coord.y) * int(tbo_size.x) + int(tbo_coord.x);
color = texelFetch(tbo, tbo_offset).rabg;
}
)";
d24s8_abgr_shader.Create(vs_source.data(), fs_source.c_str());
OpenGLState state = OpenGLState::GetCurState();
GLuint old_program = state.draw.shader_program;
state.draw.shader_program = d24s8_abgr_shader.handle;
state.Apply();
GLint tbo_u_id = glGetUniformLocation(d24s8_abgr_shader.handle, "tbo");
ASSERT(tbo_u_id != -1);
glUniform1i(tbo_u_id, 0);
state.draw.shader_program = old_program;
state.Apply();
d24s8_abgr_tbo_size_u_id = glGetUniformLocation(d24s8_abgr_shader.handle, "tbo_size");
ASSERT(d24s8_abgr_tbo_size_u_id != -1);
d24s8_abgr_viewport_u_id = glGetUniformLocation(d24s8_abgr_shader.handle, "viewport");
ASSERT(d24s8_abgr_viewport_u_id != -1);
}
~PixelBufferD24S8toABGR() {}
void Reinterpret(GLuint src_tex, const Common::Rectangle<u32>& src_rect, GLuint read_fb_handle,
GLuint dst_tex, const Common::Rectangle<u32>& dst_rect,
GLuint draw_fb_handle) override {
OpenGLState prev_state = OpenGLState::GetCurState();
SCOPE_EXIT({ prev_state.Apply(); });
OpenGLState state;
state.draw.read_framebuffer = read_fb_handle;
state.draw.draw_framebuffer = draw_fb_handle;
state.Apply();
glBindBuffer(GL_PIXEL_PACK_BUFFER, d24s8_abgr_buffer.handle);
GLsizeiptr target_pbo_size =
static_cast<GLsizeiptr>(src_rect.GetWidth()) * src_rect.GetHeight() * 4;
if (target_pbo_size > d24s8_abgr_buffer_size) {
d24s8_abgr_buffer_size = target_pbo_size * 2;
glBufferData(GL_PIXEL_PACK_BUFFER, d24s8_abgr_buffer_size, nullptr, GL_STREAM_COPY);
}
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D,
src_tex, 0);
glReadPixels(static_cast<GLint>(src_rect.left), static_cast<GLint>(src_rect.bottom),
static_cast<GLsizei>(src_rect.GetWidth()),
static_cast<GLsizei>(src_rect.GetHeight()), GL_DEPTH_STENCIL,
GL_UNSIGNED_INT_24_8, 0);
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
// PBO now contains src_tex in RABG format
state.draw.shader_program = d24s8_abgr_shader.handle;
state.draw.vertex_array = attributeless_vao.handle;
state.viewport.x = static_cast<GLint>(dst_rect.left);
state.viewport.y = static_cast<GLint>(dst_rect.bottom);
state.viewport.width = static_cast<GLsizei>(dst_rect.GetWidth());
state.viewport.height = static_cast<GLsizei>(dst_rect.GetHeight());
state.Apply();
OGLTexture tbo;
tbo.Create();
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_BUFFER, tbo.handle);
glTexBuffer(GL_TEXTURE_BUFFER, GL_RGBA8, d24s8_abgr_buffer.handle);
glUniform2f(d24s8_abgr_tbo_size_u_id, static_cast<GLfloat>(src_rect.GetWidth()),
static_cast<GLfloat>(src_rect.GetHeight()));
glUniform4f(d24s8_abgr_viewport_u_id, static_cast<GLfloat>(state.viewport.x),
static_cast<GLfloat>(state.viewport.y),
static_cast<GLfloat>(state.viewport.width),
static_cast<GLfloat>(state.viewport.height));
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst_tex,
0);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0,
0);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glBindTexture(GL_TEXTURE_BUFFER, 0);
}
private:
OGLVertexArray attributeless_vao;
OGLBuffer d24s8_abgr_buffer;
GLsizeiptr d24s8_abgr_buffer_size;
OGLProgram d24s8_abgr_shader;
GLint d24s8_abgr_tbo_size_u_id;
GLint d24s8_abgr_viewport_u_id;
};
class ShaderD24S8toRGBA8 final : public FormatReinterpreterBase {
public:
ShaderD24S8toRGBA8() {
constexpr std::string_view vs_source = R"(
out vec2 dst_coord;
uniform mediump ivec2 dst_size;
const vec2 vertices[4] =
vec2[4](vec2(-1.0, -1.0), vec2(1.0, -1.0), vec2(-1.0, 1.0), vec2(1.0, 1.0));
void main() {
gl_Position = vec4(vertices[gl_VertexID], 0.0, 1.0);
dst_coord = (vertices[gl_VertexID] / 2.0 + 0.5) * vec2(dst_size);
}
)";
constexpr std::string_view fs_source = R"(
in mediump vec2 dst_coord;
out lowp vec4 frag_color;
uniform highp sampler2D depth;
uniform lowp usampler2D stencil;
uniform mediump ivec2 dst_size;
uniform mediump ivec2 src_size;
uniform mediump ivec2 src_offset;
void main() {
mediump ivec2 tex_coord;
if (src_size == dst_size) {
tex_coord = ivec2(dst_coord);
} else {
highp int tex_index = int(dst_coord.y) * dst_size.x + int(dst_coord.x);
mediump int y = tex_index / src_size.x;
tex_coord = ivec2(tex_index - y * src_size.x, y);
}
tex_coord -= src_offset;
highp uint depth_val =
uint(texelFetch(depth, tex_coord, 0).x * (exp2(32.0) - 1.0));
lowp uint stencil_val = texelFetch(stencil, tex_coord, 0).x;
highp uvec4 components =
uvec4(stencil_val, (uvec3(depth_val) >> uvec3(24u, 16u, 8u)) & 0x000000FFu);
frag_color = vec4(components) / (exp2(8.0) - 1.0);
}
)";
program.Create(vs_source.data(), fs_source.data());
dst_size_loc = glGetUniformLocation(program.handle, "dst_size");
src_size_loc = glGetUniformLocation(program.handle, "src_size");
src_offset_loc = glGetUniformLocation(program.handle, "src_offset");
vao.Create();
auto state = OpenGLState::GetCurState();
auto cur_program = state.draw.shader_program;
state.draw.shader_program = program.handle;
state.Apply();
glUniform1i(glGetUniformLocation(program.handle, "stencil"), 1);
state.draw.shader_program = cur_program;
state.Apply();
// Nvidia seem to be the only one to support D24S8 views, at least on windows
// so for everyone else it will do an intermediate copy before running through the shader
std::string_view vendor{reinterpret_cast<const char*>(glGetString(GL_VENDOR))};
if (vendor.find("NVIDIA") != vendor.npos) {
use_texture_view = true;
} else {
LOG_INFO(Render_OpenGL,
"Texture views are unsupported, reinterpretation will do intermediate copy");
temp_tex.Create();
}
}
void Reinterpret(GLuint src_tex, const Common::Rectangle<u32>& src_rect, GLuint read_fb_handle,
GLuint dst_tex, const Common::Rectangle<u32>& dst_rect,
GLuint draw_fb_handle) override {
OpenGLState prev_state = OpenGLState::GetCurState();
SCOPE_EXIT({ prev_state.Apply(); });
OpenGLState state;
state.texture_units[0].texture_2d = src_tex;
if (use_texture_view) {
temp_tex.Create();
glActiveTexture(GL_TEXTURE1);
glTextureView(temp_tex.handle, GL_TEXTURE_2D, src_tex, GL_DEPTH24_STENCIL8, 0, 1, 0, 1);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
} else if (src_rect.top > temp_rect.top || src_rect.right > temp_rect.right) {
temp_tex.Release();
temp_tex.Create();
state.texture_units[1].texture_2d = temp_tex.handle;
state.Apply();
glActiveTexture(GL_TEXTURE1);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_DEPTH24_STENCIL8, src_rect.right, src_rect.top);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
temp_rect = src_rect;
}
state.texture_units[1].texture_2d = temp_tex.handle;
state.draw.draw_framebuffer = draw_fb_handle;
state.draw.shader_program = program.handle;
state.draw.vertex_array = vao.handle;
state.viewport = {static_cast<GLint>(dst_rect.left), static_cast<GLint>(dst_rect.bottom),
static_cast<GLsizei>(dst_rect.GetWidth()),
static_cast<GLsizei>(dst_rect.GetHeight())};
state.Apply();
glActiveTexture(GL_TEXTURE1);
if (!use_texture_view) {
glCopyImageSubData(src_tex, GL_TEXTURE_2D, 0, src_rect.left, src_rect.bottom, 0,
temp_tex.handle, GL_TEXTURE_2D, 0, src_rect.left, src_rect.bottom, 0,
src_rect.GetWidth(), src_rect.GetHeight(), 1);
}
glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_STENCIL_TEXTURE_MODE, GL_STENCIL_INDEX);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst_tex,
0);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0,
0);
glUniform2i(dst_size_loc, dst_rect.GetWidth(), dst_rect.GetHeight());
glUniform2i(src_size_loc, src_rect.GetWidth(), src_rect.GetHeight());
glUniform2i(src_offset_loc, src_rect.left, src_rect.bottom);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
if (use_texture_view) {
temp_tex.Release();
}
}
private:
bool use_texture_view{};
OGLProgram program{};
GLint dst_size_loc{-1}, src_size_loc{-1}, src_offset_loc{-1};
OGLVertexArray vao{};
OGLTexture temp_tex{};
Common::Rectangle<u32> temp_rect{0, 0, 0, 0};
};
FormatReinterpreterOpenGL::FormatReinterpreterOpenGL() {
const std::string_view vendor{reinterpret_cast<const char*>(glGetString(GL_VENDOR))};
const std::string_view version{reinterpret_cast<const char*>(glGetString(GL_VERSION))};
// Fallback to PBO path on obsolete intel drivers
// intel`s GL_VERSION string - `3.3.0 - Build 25.20.100.6373`
const bool intel_broken_drivers =
vendor.find("Intel") != vendor.npos && (std::atoi(version.substr(14, 2).data()) < 30);
if ((!intel_broken_drivers && GLAD_GL_ARB_stencil_texturing && GLAD_GL_ARB_texture_storage &&
GLAD_GL_ARB_copy_image) ||
GLES) {
reinterpreters.emplace(PixelFormatPair{PixelFormat::RGBA8, PixelFormat::D24S8},
std::make_unique<ShaderD24S8toRGBA8>());
LOG_INFO(Render_OpenGL, "Using shader for D24S8 to RGBA8 reinterpretation");
} else {
reinterpreters.emplace(PixelFormatPair{PixelFormat::RGBA8, PixelFormat::D24S8},
std::make_unique<PixelBufferD24S8toABGR>());
LOG_INFO(Render_OpenGL, "Using pbo for D24S8 to RGBA8 reinterpretation");
}
reinterpreters.emplace(PixelFormatPair{PixelFormat::RGB5A1, PixelFormat::RGBA4},
std::make_unique<RGBA4toRGB5A1>());
FormatReinterpreterVulkan::FormatReinterpreterVulkan() {
}
FormatReinterpreterOpenGL::~FormatReinterpreterOpenGL() = default;
std::pair<FormatReinterpreterOpenGL::ReinterpreterMap::iterator,
FormatReinterpreterOpenGL::ReinterpreterMap::iterator>
FormatReinterpreterOpenGL::GetPossibleReinterpretations(PixelFormat dst_format) {
std::pair<FormatReinterpreterVulkan::ReinterpreterMap::iterator,
FormatReinterpreterVulkan::ReinterpreterMap::iterator>
FormatReinterpreterVulkan::GetPossibleReinterpretations(PixelFormat dst_format) {
return reinterpreters.equal_range(dst_format);
}

View File

@@ -51,7 +51,7 @@ class FormatReinterpreterVulkan : NonCopyable {
public:
explicit FormatReinterpreterVulkan();
~FormatReinterpreterVulkan();
~FormatReinterpreterVulkan() = default;
std::pair<ReinterpreterMap::iterator, ReinterpreterMap::iterator> GetPossibleReinterpretations(
SurfaceParams::PixelFormat dst_format);

View File

@@ -13,8 +13,7 @@
namespace Vulkan {
/// The global Vulkan instance
class VKInstance
{
class VKInstance {
public:
VKInstance() = default;
~VKInstance();

View File

@@ -11,27 +11,85 @@
namespace Vulkan {
PipelineBuilder::PipelineBuilder() {
vertex_input_state = vk::PipelineVertexInputStateCreateInfo{
{}, HardwareVertex::binding_desc, HardwareVertex::attribute_desc
};
PipelineLayoutBuilder::PipelineLayoutBuilder() {
Clear();
}
/* Include all required pointers to the pipeline info structure */
vk::GraphicsPipelineCreateInfo pipeline_info{
{}, 0, shader_stages.data(), &vertex_input_state, &input_assembly, nullptr,
&viewport_state, &rasterization_state, &multisample_info, &depth_state,
&blend_state, &dynamic_info, nullptr, nullptr };
void PipelineLayoutBuilder::Clear() {
pipeline_layout_info = vk::PipelineLayoutCreateInfo{};
}
vk::PipelineLayout PipelineLayoutBuilder::Build() {
auto& device = g_vk_instace->GetDevice();
auto result = device.createPipelineLayout(pipeline_layout_info);
if (result) {
LOG_ERROR(Render_Vulkan, "Failed to create pipeline layout");
return VK_NULL_HANDLE;
}
Clear();
return result;
}
void PipelineLayoutBuilder::AddDescriptorSet(vk::DescriptorSetLayout layout) {
assert(pipeline_layout_info.setLayoutCount < MAX_SETS);
sets[pipeline_layout_info.setLayoutCount++] = layout;
pipeline_layout_info.pSetLayouts = sets.data();
}
void PipelineLayoutBuilder::AddPushConstants(vk::ShaderStageFlags stages, u32 offset, u32 size) {
assert(pipeline_layout_info.pushConstantRangeCount < MAX_PUSH_CONSTANTS);
push_constants[pipeline_layout_info.pushConstantRangeCount++] = {stages, offset, size};
pipeline_layout_info.pPushConstantRanges = push_constants.data();
}
PipelineBuilder::PipelineBuilder() {
Clear();
}
void PipelineBuilder::Clear() {
pipeline_info = vk::GraphicsPipelineCreateInfo{};
shader_stages.clear();
vertex_input_state = vk::PipelineVertexInputStateCreateInfo{};
input_assembly = vk::PipelineInputAssemblyStateCreateInfo{};
rasterization_state = vk::PipelineRasterizationStateCreateInfo{};
depth_state = vk::PipelineDepthStencilStateCreateInfo{};
blend_state = vk::PipelineColorBlendStateCreateInfo{};
blend_attachment = vk::PipelineColorBlendAttachmentState{};
dynamic_info = vk::PipelineDynamicStateCreateInfo{};
dynamic_states.fill({});
viewport_state = vk::PipelineViewportStateCreateInfo{};
multisample_info = vk::PipelineMultisampleStateCreateInfo{};
// Set defaults
SetNoCullRasterizationState();
SetNoDepthTestState();
SetNoBlendingState();
SetPrimitiveTopology(vk::PrimitiveTopology::eTriangleList);
// Have to be specified even if dynamic
SetViewport(0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f);
SetScissorRect(0, 0, 1, 1);
SetBlendConstants(1.0f, 1.0f, 1.0f, 1.0f);
SetMultisamples(vk::SampleCountFlagBits::e1, false);
}
vk::Pipeline PipelineBuilder::Build() {
auto& device = g_vk_instace->GetDevice();
auto result = device.createGraphicsPipeline({}, pipeline_info);
auto result = device.createGraphicsPipeline({}, pipeline_info);
if (result.result != vk::Result::eSuccess) {
LOG_CRITICAL(Render_Vulkan, "Failed to build vulkan pipeline!");
UNREACHABLE();
}
Clear();
return result.value;
}
@@ -54,6 +112,23 @@ void PipelineBuilder::SetShaderStage(vk::ShaderStageFlagBits stage, vk::ShaderMo
}
}
void PipelineBuilder::AddVertexBuffer(u32 binding, u32 stride, vk::VertexInputRate input_rate,
std::span<vk::VertexInputAttributeDescription> attributes) {
assert(vertex_input_state.vertexAttributeDescriptionCount + attributes.size() < MAX_VERTEX_BUFFERS);
// Copy attributes to private array
auto loc = vertex_attributes.begin() + vertex_input_state.vertexAttributeDescriptionCount;
std::copy(attributes.begin(), attributes.end(), loc);
vertex_buffers[vertex_input_state.vertexBindingDescriptionCount++] = {binding, stride, input_rate};
vertex_input_state.vertexAttributeDescriptionCount += attributes.size();
vertex_input_state.pVertexBindingDescriptions = vertex_buffers.data();
vertex_input_state.pVertexAttributeDescriptions = vertex_attributes.data();
pipeline_info.pVertexInputState = &vertex_input_state;
}
void PipelineBuilder::SetPrimitiveTopology(vk::PrimitiveTopology topology, bool enable_primitive_restart) {
input_assembly.topology = topology;
input_assembly.primitiveRestartEnable = enable_primitive_restart;
@@ -65,32 +140,42 @@ void PipelineBuilder::SetRasterizationState(vk::PolygonMode polygon_mode, vk::Cu
rasterization_state.polygonMode = polygon_mode;
rasterization_state.cullMode = cull_mode;
rasterization_state.frontFace = front_face;
pipeline_info.pRasterizationState = &rasterization_state;
}
void PipelineBuilder::SetLineWidth(float width) {
rasterization_state.lineWidth = width;
pipeline_info.pRasterizationState = &rasterization_state;
}
void PipelineBuilder::SetMultisamples(u32 multisamples, bool per_sample_shading) {
multisample_info.rasterizationSamples = static_cast<vk::SampleCountFlagBits>(multisamples);
void PipelineBuilder::SetMultisamples(vk::SampleCountFlagBits samples, bool per_sample_shading) {
multisample_info.rasterizationSamples = samples;
multisample_info.sampleShadingEnable = per_sample_shading;
multisample_info.minSampleShading = (multisamples > 1) ? 1.0f : 0.0f;
multisample_info.minSampleShading = (static_cast<u32>(samples) > 1) ? 1.0f : 0.0f;
pipeline_info.pMultisampleState = &multisample_info;
}
void PipelineBuilder::SetNoCullRasterizationState() {
SetRasterizationState(vk::PolygonMode::eFill, vk::CullModeFlagBits::eNone, vk::FrontFace::eClockwise);
}
void PipelineBuilder::SetDepthState(bool depth_test, bool depth_write, vk::CompareOp compare_op) {
depth_state.depthTestEnable = depth_test;
depth_state.depthWriteEnable = depth_write;
depth_state.depthCompareOp = compare_op;
pipeline_info.pDepthStencilState = &depth_state;
}
void PipelineBuilder::SetStencilState(bool stencil_test, vk::StencilOpState front, vk::StencilOpState back) {
depth_state.stencilTestEnable = stencil_test;
depth_state.front = front;
depth_state.back = back;
pipeline_info.pDepthStencilState = &depth_state;
}
void PipelineBuilder::SetBlendConstants(float r, float g, float b, float a) {
blend_state.blendConstants = std::array<float, 4>{r, g, b, a};
pipeline_info.pColorBlendState = &blend_state;
}
void PipelineBuilder::SetBlendAttachment(bool blend_enable, vk::BlendFactor src_factor, vk::BlendFactor dst_factor,
@@ -108,6 +193,7 @@ void PipelineBuilder::SetBlendAttachment(bool blend_enable, vk::BlendFactor src_
blend_state.attachmentCount = 1;
blend_state.pAttachments = &blend_attachment;
pipeline_info.pColorBlendState = &blend_state;
}
void PipelineBuilder::SetNoBlendingState() {
@@ -116,29 +202,33 @@ void PipelineBuilder::SetNoBlendingState() {
vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA);
}
void PipelineBuilder::AddDynamicState(vk::DynamicState state) {
if (dynamic_info.dynamicStateCount < MAX_DYNAMIC_STATES) {
dynamic_states[dynamic_info.dynamicStateCount] = state;
dynamic_info.dynamicStateCount++;
dynamic_info.pDynamicStates = dynamic_states.data();
return;
void PipelineBuilder::SetDynamicStates(std::span<vk::DynamicState> states) {
if (states.size() > MAX_DYNAMIC_STATES) {
LOG_ERROR(Render_Vulkan, "Cannot include more dynamic states!");
UNREACHABLE();
}
LOG_ERROR(Render_Vulkan, "Cannot include more dynamic states!");
UNREACHABLE();
// Copy the state data
std::copy(states.begin(), states.end(), dynamic_states.begin());
dynamic_info.dynamicStateCount = states.size();
dynamic_info.pDynamicStates = dynamic_states.data();
pipeline_info.pDynamicState = &dynamic_info;
return;
}
void PipelineBuilder::SetViewport(float x, float y, float width, float height, float min_depth, float max_depth) {
viewport = vk::Viewport{ x, y, width, height, min_depth, max_depth };
viewport_state.pViewports = &viewport;
viewport_state.viewportCount = 1;
pipeline_info.pViewportState = &viewport_state;
}
void PipelineBuilder::SetScissorRect(s32 x, s32 y, u32 width, u32 height) {
scissor = vk::Rect2D{{x, y}, {width, height}};
viewport_state.pScissors = &scissor;
viewport_state.scissorCount = 1u;
viewport_state.pScissors = &scissor;
pipeline_info.pViewportState = &viewport_state;
}
} // namespace Vulkan

View File

@@ -15,25 +15,46 @@
namespace Vulkan {
constexpr u32 MAX_DYNAMIC_STATES = 14;
constexpr u32 MAX_SHADER_STAGES = 3;
class PipelineLayoutBuilder {
public:
PipelineLayoutBuilder();
~PipelineLayoutBuilder() = default;
void Clear();
vk::PipelineLayout Build();
void AddDescriptorSet(vk::DescriptorSetLayout layout);
void AddPushConstants(vk::ShaderStageFlags stages, u32 offset, u32 size);
private:
static constexpr u32 MAX_SETS = 8;
static constexpr u32 MAX_PUSH_CONSTANTS = 1;
vk::PipelineLayoutCreateInfo pipeline_layout_info;
std::array<vk::DescriptorSetLayout, MAX_SETS> sets;
std::array<vk::PushConstantRange, MAX_PUSH_CONSTANTS> push_constants;
};
class PipelineBuilder {
public:
PipelineBuilder();
~PipelineBuilder() = default;
void Clear();
vk::Pipeline Build();
void SetPipelineLayout(vk::PipelineLayout layout);
void AddVertexBuffer(u32 binding, u32 stride, vk::VertexInputRate input_rate,
const std::span<vk::VertexInputAttributeDescription> attributes);
void SetShaderStage(vk::ShaderStageFlagBits stage, vk::ShaderModule module);
void SetPrimitiveTopology(vk::PrimitiveTopology topology, bool enable_primitive_restart = false);
void SetLineWidth(float width);
void SetMultisamples(u32 multisamples, bool per_sample_shading);
void SetMultisamples(vk::SampleCountFlagBits samples, bool per_sample_shading);
void SetRasterizationState(vk::PolygonMode polygon_mode, vk::CullModeFlags cull_mode,
vk::FrontFace front_face);
void SetNoCullRasterizationState();
void SetDepthState(bool depth_test, bool depth_write, vk::CompareOp compare_op);
void SetStencilState(bool stencil_test, vk::StencilOpState front, vk::StencilOpState back);
void SetNoDepthTestState();
@@ -47,14 +68,21 @@ public:
void SetViewport(float x, float y, float width, float height, float min_depth, float max_depth);
void SetScissorRect(s32 x, s32 y, u32 width, u32 height);
void AddDynamicState(vk::DynamicState state);
void SetMultisamples(vk::SampleCountFlagBits samples);
void SetDynamicStates(std::span<vk::DynamicState> states);
private:
static constexpr u32 MAX_DYNAMIC_STATES = 14;
static constexpr u32 MAX_SHADER_STAGES = 3;
static constexpr u32 MAX_VERTEX_BUFFERS = 8;
static constexpr u32 MAX_VERTEX_ATTRIBUTES = 16;
vk::GraphicsPipelineCreateInfo pipeline_info;
std::vector<vk::PipelineShaderStageCreateInfo> shader_stages;
vk::PipelineVertexInputStateCreateInfo vertex_input_state;
std::array<vk::VertexInputBindingDescription, MAX_VERTEX_BUFFERS> vertex_buffers;
std::array<vk::VertexInputAttributeDescription, MAX_VERTEX_ATTRIBUTES> vertex_attributes;
vk::PipelineInputAssemblyStateCreateInfo input_assembly;
vk::PipelineRasterizationStateCreateInfo rasterization_state;
vk::PipelineDepthStencilStateCreateInfo depth_state;

View File

@@ -76,7 +76,10 @@ RasterizerVulkan::RasterizerVulkan(Frontend::EmuWindow& emu_window) {
.usage = vk::BufferUsageFlagBits::eStorageTexelBuffer,
};
texel_buffer_info.view_formats[0] = vk::Format::eR32G32Sfloat;
texture_buffer_lut_lf.Create(texel_buffer_info);
texel_buffer_info.view_formats[1] = vk::Format::eR32G32B32A32Sfloat;
texture_buffer_lut.Create(texel_buffer_info);
// Create and bind uniform buffers
@@ -87,13 +90,15 @@ RasterizerVulkan::RasterizerVulkan(Frontend::EmuWindow& emu_window) {
};
uniform_buffer.Create(uniform_info);
state.SetUniformBuffer(BindingID::VertexUniform, &uniform_buffer, 0, uniform_size_aligned_vs);
state.SetUniformBuffer(BindingID::PicaUniform, &uniform_buffer, uniform_size_aligned_vs, uniform_size_aligned_fs);
state.SetUniformBuffer(BindingID::VertexUniform, 0, uniform_size_aligned_vs,
uniform_buffer);
state.SetUniformBuffer(BindingID::PicaUniform, uniform_size_aligned_vs, uniform_size_aligned_fs,
uniform_buffer);
// Bind texel buffers
state.SetTexelBuffer(BindingID::LutLF, &texture_buffer_lut_lf, vk::Format::eR32G32Sfloat);
state.SetTexelBuffer(BindingID::LutRG, &texture_buffer_lut, vk::Format::eR32G32Sfloat);
state.SetTexelBuffer(BindingID::LutRGBA, &texture_buffer_lut, vk::Format::eR32G32B32A32Sfloat);
state.SetTexelBuffer(BindingID::LutLF, 0, TEXTURE_BUFFER_SIZE, texture_buffer_lut_lf, 0);
state.SetTexelBuffer(BindingID::LutRG, 0, TEXTURE_BUFFER_SIZE, texture_buffer_lut, 0);
state.SetTexelBuffer(BindingID::LutRGBA, 0, TEXTURE_BUFFER_SIZE, texture_buffer_lut, 1);
// Create vertex and index buffers
VKBuffer::Info vertex_info = {
@@ -331,7 +336,7 @@ bool RasterizerVulkan::Draw(bool accelerate, bool is_indexed) {
//texture_samplers[texture_index].SyncWithConfig(texture.config);
Surface surface = res_cache.GetTextureSurface(texture);
if (surface != nullptr) {
state.SetTexture(BindingID::Tex0 + texture_index, &surface->texture);
state.SetTexture(BindingID::Tex0 + texture_index, surface->texture);
} else {
// Can occur when texture addr is null or its memory is unmapped/invalid
// HACK: In this case, the correct behaviour for the PICA is to use the last
@@ -405,13 +410,6 @@ bool RasterizerVulkan::Draw(bool accelerate, bool is_indexed) {
vertex_batch.clear();
// Reset textures in rasterizer state context because the rasterizer cache might delete them
for (unsigned texture_index = 0; texture_index < pica_textures.size(); ++texture_index) {
state.UnbindTexture(texture_index);
}
state.Apply();
// Mark framebuffer surfaces as dirty
Common::Rectangle<u32> draw_rect_unscaled{draw_rect.left / res_scale, draw_rect.top / res_scale,
draw_rect.right / res_scale,
@@ -1132,7 +1130,7 @@ bool RasterizerVulkan::AccelerateDisplay(const GPU::Regs::FramebufferConfig& con
(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;
screen_info.display_texture = src_surface->texture.GetHandle();
return true;
}

View File

@@ -280,15 +280,12 @@ private:
static constexpr std::size_t UNIFORM_BUFFER_SIZE = 2 * 1024 * 1024;
static constexpr std::size_t TEXTURE_BUFFER_SIZE = 1 * 1024 * 1024;
std::array<SamplerInfo, 3> texture_samplers;
VKBuffer vertex_buffer, uniform_buffer, index_buffer;
VKBuffer texture_buffer_lut_lf, texture_buffer_lut;
u32 uniform_buffer_alignment;
u32 uniform_size_aligned_vs, uniform_size_aligned_fs;
SamplerInfo texture_cube_sampler;
std::array<std::array<glm::vec2, 256>,
Pica::LightingRegs::NumLightingSampler> lighting_lut_data{};
std::array<glm::vec2, 128> fog_lut_data{};

View File

@@ -36,6 +36,51 @@ bool operator <(BindingID lhs, BindingID rhs) {
static_cast<u32>(rhs);
}
DescriptorUpdater::DescriptorUpdater() {
Clear();
}
void DescriptorUpdater::Clear() {
writes = {};
write_count = 0;
}
void DescriptorUpdater::Update() {
assert(write_count > 0);
auto& device = g_vk_instace->GetDevice();
device.updateDescriptorSets(writes, {});
}
void DescriptorUpdater::SetDescriptorSet(vk::DescriptorSet _set) {
set = _set;
}
void DescriptorUpdater::AddCombinedImageSamplerDescriptorWrite(u32 binding, vk::Sampler sampler,
const VKTexture& image) {
assert(write_count < MAX_WRITES && image_info_count < MAX_IMAGE_INFOS);
auto& info = image_infos[image_info_count++];
info = vk::DescriptorImageInfo{sampler, image.GetView(), image.GetLayout()};
writes[write_count++] = vk::WriteDescriptorSet{
set, binding, 0, 1, vk::DescriptorType::eCombinedImageSampler, &info
};
}
void DescriptorUpdater::AddBufferDescriptorWrite(u32 binding, vk::DescriptorType buffer_type,
u32 offset, u32 size, const VKBuffer& buffer,
const vk::BufferView& view) {
assert(write_count < MAX_WRITES && buffer_info_count < MAX_BUFFER_INFOS);
auto& info = buffer_infos[buffer_info_count++];
info = vk::DescriptorBufferInfo{buffer.GetBuffer(), offset, size};
writes[write_count++] = vk::WriteDescriptorSet{
set, binding, 0, 1, buffer_type, nullptr, &info, &view
};
}
VulkanState::VulkanState() {
// Create a dummy texture which can be used in place of a real binding.
VKTexture::Info info = {
@@ -46,20 +91,8 @@ VulkanState::VulkanState() {
.view_type = vk::ImageViewType::e2D
};
dummy_texture.Create(info);
dummy_texture.Transition(vk::ImageLayout::eShaderReadOnlyOptimal);
// Create descriptor pool
// TODO: Choose sizes more wisely
const std::array<vk::DescriptorPoolSize, 3> pool_sizes{{
{ vk::DescriptorType::eUniformBuffer, 32 },
{ vk::DescriptorType::eCombinedImageSampler, 32 },
{ vk::DescriptorType::eStorageTexelBuffer, 32 },
}};
auto& device = g_vk_instace->GetDevice();
vk::DescriptorPoolCreateInfo pool_create_info({}, 1024, pool_sizes);
desc_pool = device.createDescriptorPoolUnique(pool_create_info);
placeholder.Create(info);
placeholder.Transition(vk::ImageLayout::eShaderReadOnlyOptimal);
// Create texture sampler
auto props = g_vk_instace->GetPhysicalDevice().getProperties();
@@ -71,6 +104,8 @@ VulkanState::VulkanState() {
false, vk::CompareOp::eAlways, {}, {},
vk::BorderColor::eIntOpaqueBlack, false
};
auto& device = g_vk_instace->GetDevice();
sampler = device.createSamplerUnique(sampler_info);
// Compile trivial vertex shader
@@ -105,60 +140,44 @@ void VulkanState::SetVertexBuffer(VKBuffer* buffer, vk::DeviceSize offset) {
dirty_flags |= DirtyFlags::VertexBuffer;
}
void VulkanState::SetUniformBuffer(BindingID id, VKBuffer* buffer, u32 offset, u32 size) {
void VulkanState::SetUniformBuffer(BindingID id, u32 offset, u32 size, const VKBuffer& buffer) {
assert(id < BindingID::Tex0);
u32 index = static_cast<u32>(id);
u32 binding = static_cast<u32>(id);
auto& binding = bindings[index];
auto old_buffer = std::get<VKBuffer*>(binding.resource);
if (old_buffer != buffer) {
binding.resource = buffer;
dirty_flags |= DirtyFlags::Uniform;
binding.dirty = true;
}
updater.SetDescriptorSet(g_vk_task_scheduler->GetDescriptorSet(0));
updater.AddBufferDescriptorWrite(binding, vk::DescriptorType::eUniformBuffer, offset, size, buffer);
}
void VulkanState::SetTexture(BindingID id, VKTexture* image) {
void VulkanState::SetTexture(BindingID id, const VKTexture& image) {
assert(id > BindingID::PicaUniform && id < BindingID::LutLF);
u32 index = static_cast<u32>(id);
u32 binding = static_cast<u32>(id);
auto& binding = bindings[index];
auto old_image = std::get<VKTexture*>(binding.resource);
if (old_image != image) {
binding.resource = image;
dirty_flags |= DirtyFlags::Texture;
binding.dirty = true;
}
updater.SetDescriptorSet(g_vk_task_scheduler->GetDescriptorSet(1));
updater.AddCombinedImageSamplerDescriptorWrite(binding, sampler.get(), image);
}
void VulkanState::SetTexelBuffer(BindingID id, VKBuffer* buffer, vk::Format view_format) {
void VulkanState::SetTexelBuffer(BindingID id, u32 offset, u32 size, const VKBuffer& buffer, u32 view_index) {
assert(id > BindingID::TexCube);
u32 index = static_cast<u32>(id);
u32 binding = static_cast<u32>(id);
auto& binding = bindings[index];
auto old_buffer = std::get<VKBuffer*>(binding.resource);
if (old_buffer != buffer) {
auto& device = g_vk_instace->GetDevice();
binding.resource = buffer;
binding.buffer_view = device.createBufferViewUnique({{}, buffer->GetBuffer(), view_format});
dirty_flags |= DirtyFlags::TexelBuffer;
binding.dirty = true;
}
updater.SetDescriptorSet(g_vk_task_scheduler->GetDescriptorSet(2));
updater.AddBufferDescriptorWrite(binding, vk::DescriptorType::eStorageTexelBuffer,
offset, size, buffer, buffer.GetView(view_index));
}
void VulkanState::UnbindTexture(VKTexture* image) {
void VulkanState::UnbindTexture(const VKTexture& image) {
updater.SetDescriptorSet(g_vk_task_scheduler->GetDescriptorSet(1));
for (auto i = u32(BindingID::Tex0); i <= u32(BindingID::TexCube); i++) {
auto current_image = std::get<VKTexture*>(bindings[i].resource);
if (current_image == image) {
UnbindTexture(i);
auto view = updater.GetResource<vk::ImageView>(i);
if (view == image.GetView()) {
updater.AddCombinedImageSamplerDescriptorWrite(i, sampler.get(), placeholder);
}
}
}
void VulkanState::UnbindTexture(u32 index) {
bindings[index].resource = &dummy_texture;
dirty_flags |= DirtyFlags::Texture;
updater.SetDescriptorSet(g_vk_task_scheduler->GetDescriptorSet(1));
updater.AddCombinedImageSamplerDescriptorWrite(index, sampler.get(), placeholder);
}
void VulkanState::BeginRendering(Attachment color, Attachment depth_stencil) {
@@ -383,7 +402,7 @@ vk::ShaderModule VulkanState::CompileShader(const std::string& source, vk::Shade
void VulkanState::Apply() {
// Update resources in descriptor sets if changed
UpdateDescriptorSet();
updater.Update();
// Re-apply dynamic parts of the pipeline
auto command_buffer = g_vk_task_scheduler->GetCommandBuffer();
@@ -444,21 +463,21 @@ void VulkanState::Apply() {
}
void VulkanState::ConfigureDescriptorSets() {
// Define the descriptor sets we will be using
std::array<vk::DescriptorSetLayoutBinding, 2> ubo_set = {{
// Draw descriptor sets
std::array<vk::DescriptorSetLayoutBinding, 2> ubo_set{{
{ 0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex |
vk::ShaderStageFlagBits::eGeometry | vk::ShaderStageFlagBits::eFragment }, // shader_data
{ 1, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex } // pica_uniforms
}};
std::array<vk::DescriptorSetLayoutBinding, 4> texture_set = {{
std::array<vk::DescriptorSetLayoutBinding, 4> texture_set{{
{ 0, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment }, // tex0
{ 1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment }, // tex1
{ 2, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment }, // tex2
{ 3, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment }, // tex_cube
}};
std::array<vk::DescriptorSetLayoutBinding, 3> lut_set = {{
std::array<vk::DescriptorSetLayoutBinding, 3> lut_set{{
{ 0, vk::DescriptorType::eStorageTexelBuffer, 1, vk::ShaderStageFlagBits::eFragment }, // texture_buffer_lut_lf
{ 1, vk::DescriptorType::eStorageTexelBuffer, 1, vk::ShaderStageFlagBits::eFragment }, // texture_buffer_lut_rg
{ 2, vk::DescriptorType::eStorageTexelBuffer, 1, vk::ShaderStageFlagBits::eFragment } // texture_buffer_lut_rgba
@@ -476,13 +495,9 @@ void VulkanState::ConfigureDescriptorSets() {
descriptor_layouts[i] = device.createDescriptorSetLayout(create_infos[i]);
}
vk::DescriptorSetAllocateInfo alloc_info(desc_pool.get(), descriptor_layouts);
descriptor_sets = device.allocateDescriptorSetsUnique(alloc_info);
// Create the standard descriptor set layout
vk::PipelineLayoutCreateInfo layout_info({}, descriptor_layouts);
pipeline_layout = device.createPipelineLayoutUnique(layout_info);
}
void VulkanState::ConfigurePipeline() {
@@ -505,7 +520,7 @@ void VulkanState::ConfigurePipeline() {
// Enable every required dynamic state
std::array<vk::DynamicState, MAX_DYNAMIC_STATES> dynamic_states{
std::array<vk::DynamicState, 14> dynamic_states{
vk::DynamicState::eDepthCompareOp, vk::DynamicState::eLineWidth,
vk::DynamicState::eDepthTestEnable, vk::DynamicState::eColorWriteEnableEXT,
vk::DynamicState::eStencilTestEnable, vk::DynamicState::eStencilOp,
@@ -515,61 +530,14 @@ void VulkanState::ConfigurePipeline() {
vk::DynamicState::eLogicOpEXT, vk::DynamicState::eFrontFace
};
for (auto& state : dynamic_states) {
builder.AddDynamicState(state);
}
builder.SetDynamicStates(dynamic_states);
// Configure vertex buffer
auto attributes = HardwareVertex::attribute_desc;
builder.AddVertexBuffer(0, sizeof(HardwareVertex), vk::VertexInputRate::eVertex, attributes);
// Add trivial vertex shader
builder.SetShaderStage(vk::ShaderStageFlagBits::eVertex, trivial_vertex_shader.get());
}
void VulkanState::UpdateDescriptorSet() {
std::vector<vk::WriteDescriptorSet> writes;
std::vector<vk::DescriptorBufferInfo> buffer_infos;
std::vector<vk::DescriptorImageInfo> image_infos;
auto& device = g_vk_instace->GetDevice();
// Check if any resource has been updated
if (dirty_flags & DirtyFlags::Uniform) {
for (int i = 0; i < 2; i++) {
if (bindings[i].dirty) {
auto buffer = std::get<VKBuffer*>(bindings[i].resource);
buffer_infos.emplace_back(buffer->GetBuffer(), 0, VK_WHOLE_SIZE);
writes.emplace_back(descriptor_sets[i].get(), i, 0, 1, vk::DescriptorType::eUniformBuffer,
nullptr, &buffer_infos.back(), nullptr);
bindings[i].dirty = false;
}
}
}
if (dirty_flags & DirtyFlags::Texture) {
for (int i = 2; i < 6; i++) {
if (bindings[i].dirty) {
auto texture = std::get<VKTexture*>(bindings[i].resource);
image_infos.emplace_back(sampler.get(), texture->GetView(), vk::ImageLayout::eShaderReadOnlyOptimal);
writes.emplace_back(descriptor_sets[i].get(), i, 0, 1, vk::DescriptorType::eCombinedImageSampler,
&image_infos.back());
bindings[i].dirty = false;
}
}
}
if (dirty_flags & DirtyFlags::TexelBuffer) {
for (int i = 6; i < 9; i++) {
if (bindings[i].dirty) {
auto buffer = std::get<VKBuffer*>(bindings[i].resource);
buffer_infos.emplace_back(buffer->GetBuffer(), 0, VK_WHOLE_SIZE);
writes.emplace_back(descriptor_sets[i].get(), i, 0, 1, vk::DescriptorType::eStorageTexelBuffer,
nullptr, &buffer_infos.back(), &bindings[i].buffer_view.get());
bindings[i].dirty = false;
}
}
}
if (!writes.empty()) {
device.updateDescriptorSets(writes, {});
}
}
} // namespace Vulkan

View File

@@ -64,6 +64,41 @@ struct Attachment {
constexpr u32 DESCRIPTOR_SET_LAYOUT_COUNT = 3;
class DescriptorUpdater {
enum : u32
{
MAX_WRITES = 16,
MAX_IMAGE_INFOS = 8,
MAX_BUFFER_INFOS = 4,
MAX_VIEWS = 4,
MAX_SETS = 6
};
public:
DescriptorUpdater();
~DescriptorUpdater() = default;
void Clear();
void Update();
template <typename T>
T GetResource(u32 binding);
void SetDescriptorSet(vk::DescriptorSet set);
void AddCombinedImageSamplerDescriptorWrite(u32 binding, vk::Sampler sampler, const VKTexture& image);
void AddBufferDescriptorWrite(u32 binding, vk::DescriptorType buffer_type, u32 offset, u32 size,
const VKBuffer& buffer, const vk::BufferView& view = VK_NULL_HANDLE);
private:
vk::DescriptorSet set;
std::array<vk::WriteDescriptorSet, MAX_WRITES> writes;
std::array<vk::DescriptorBufferInfo, MAX_BUFFER_INFOS> buffer_infos;
std::array<vk::DescriptorImageInfo, MAX_IMAGE_INFOS> image_infos;
std::array<vk::BufferView, MAX_VIEWS> views;
u32 write_count = 0, buffer_info_count = 0;
u32 image_info_count = 0, view_count = 0;
};
/// Tracks global Vulkan state
class VulkanState {
public:
@@ -73,6 +108,7 @@ public:
/// Initialize object to its initial state
static void Create();
static VulkanState& Get();
static vk::ShaderModule CompileShader(const std::string& source, vk::ShaderStageFlagBits stage);
/// Query state
bool DepthTestEnabled() const { return depth_enabled && depth_writes; }
@@ -88,7 +124,7 @@ public:
void SetStencilWrite(u32 mask);
void SetStencilInput(u32 mask);
void SetStencilTest(bool enable, vk::StencilOp fail, vk::StencilOp pass, vk::StencilOp depth_fail,
vk::CompareOp compare, u32 ref);
vk::CompareOp compare, u32 ref);
void SetDepthWrite(bool enable);
void SetDepthTest(bool enable, vk::CompareOp compare);
void SetColorMask(bool red, bool green, bool blue, bool alpha);
@@ -103,10 +139,10 @@ public:
void EndRendering();
/// Configure shader resources
void SetUniformBuffer(BindingID id, VKBuffer* buffer, u32 offset, u32 size);
void SetTexture(BindingID id, VKTexture* texture);
void SetTexelBuffer(BindingID id, VKBuffer* buffer, vk::Format view_format);
void UnbindTexture(VKTexture* image);
void SetUniformBuffer(BindingID id, u32 offset, u32 size, const VKBuffer& buffer);
void SetTexture(BindingID id, const VKTexture& texture);
void SetTexelBuffer(BindingID id, u32 offset, u32 size, const VKBuffer& buffer, u32 view_index);
void UnbindTexture(const VKTexture& image);
void UnbindTexture(u32 index);
/// Apply all dirty state to the current Vulkan command buffer
@@ -115,27 +151,18 @@ public:
private:
void ConfigureDescriptorSets();
void ConfigurePipeline();
void UpdateDescriptorSet();
vk::ShaderModule CompileShader(const std::string& source, vk::ShaderStageFlagBits stage);
private:
struct Binding {
bool dirty{};
std::variant<VKBuffer*, VKTexture*> resource{};
vk::UniqueBufferView buffer_view{};
};
DirtyFlags dirty_flags;
bool rendering = false;
VKTexture dummy_texture;
DescriptorUpdater updater;
VKTexture placeholder;
vk::UniqueSampler sampler;
// Vertex buffer
VKBuffer* vertex_buffer{}, * index_buffer{};
vk::DeviceSize vertex_offset, index_offset;
std::array<Binding, 9> bindings;
std::vector<vk::UniqueDescriptorSet> descriptor_sets;
vk::UniqueDescriptorPool desc_pool;
// Viewport
vk::Viewport viewport{0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f};
vk::CullModeFlags cull_mode{};
vk::FrontFace front_face{};
@@ -143,6 +170,7 @@ private:
vk::LogicOp logic_op{};
std::array<float, 4> blend_constants{};
bool rendering = false;
u32 stencil_write_mask{}, stencil_input_mask{}, stencil_ref{};
bool depth_enabled{}, depth_writes{}, stencil_enabled{}, stencil_writes{};
vk::StencilOp fail_op, pass_op, depth_fail_op;
@@ -159,4 +187,20 @@ private:
std::unordered_map<PipelineCacheKey, vk::UniquePipeline> pipelines;
};
template <typename T>
T DescriptorUpdater::GetResource(u32 binding) {
for (auto& write : writes) {
if (write.dstBinding == binding) {
if constexpr (std::is_same_v<T, vk::ImageView>) {
return write.pImageInfo[0].imageView;
}
else if constexpr (std::is_same_v<T, vk::Buffer>) {
return write.pBufferInfo[0].buffer;
}
}
}
return VK_NULL_HANDLE;
}
} // namespace Vulkan

View File

@@ -189,17 +189,11 @@ void VKSwapChain::SetupImages() {
// Create the swapchain buffers containing the image and imageview
swapchain_images.resize(images.size());
for (int i = 0; i < swapchain_images.size(); i++)
{
vk::ImageViewCreateInfo color_attachment_view
(
{},
images[i],
vk::ImageViewType::e2D,
details.format.format,
{},
for (int i = 0; i < swapchain_images.size(); i++) {
vk::ImageViewCreateInfo color_attachment_view{
{}, images[i], vk::ImageViewType::e2D, details.format.format, {},
{ vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 }
);
};
// Wrap swapchain images with VKTexture
swapchain_images[i].image = images[i];

View File

@@ -9,9 +9,6 @@
namespace Vulkan {
VKTaskScheduler::VKTaskScheduler(VKSwapChain* swapchain) :
swapchain(swapchain) {}
VKTaskScheduler::~VKTaskScheduler() {
SyncToGPU();
}
@@ -50,23 +47,26 @@ bool VKTaskScheduler::Create() {
.usage = vk::BufferUsageFlagBits::eTransferSrc
};
const std::array<vk::DescriptorPoolSize, 3> pool_sizes{{
{ vk::DescriptorType::eUniformBuffer, 32 },
{ vk::DescriptorType::eCombinedImageSampler, 32 },
{ vk::DescriptorType::eStorageTexelBuffer, 32 },
}};
for (auto& task : tasks) {
// Create command buffers
vk::CommandBufferAllocateInfo buffer_info
(
command_pool.get(),
vk::CommandBufferLevel::ePrimary,
1, task.command_buffer
);
vk::CommandBufferAllocateInfo buffer_info{
command_pool.get(), vk::CommandBufferLevel::ePrimary, 1, task.command_buffer
};
task.command_buffer = device.allocateCommandBuffers(buffer_info)[0];
// Create staging buffer
task.staging.Create(staging_info);
}
// Create present semaphore
present_semaphore = device.createSemaphoreUnique({});
// Create descriptor pool
vk::DescriptorPoolCreateInfo pool_create_info({}, 1024, pool_sizes);
task.desc_pool = device.createDescriptorPoolUnique(pool_create_info);
}
// Activate the first task.
BeginTask();
@@ -74,6 +74,14 @@ bool VKTaskScheduler::Create() {
return true;
}
vk::CommandBuffer VKTaskScheduler::GetCommandBuffer() const {
return tasks[current_task].command_buffer;
}
vk::DescriptorSet VKTaskScheduler::GetDescriptorSet(u32 index) const {
return tasks[current_task].descriptor_sets[index].get();
}
void VKTaskScheduler::SyncToGPU(u64 task_index) {
// No need to sync if the GPU already has finished the task
if (tasks[task_index].task_id <= GetGPUTick()) {
@@ -106,7 +114,7 @@ void VKTaskScheduler::SyncToGPU() {
SyncToGPU(current_task);
}
void VKTaskScheduler::Submit(bool present, bool wait_completion) {
void VKTaskScheduler::Submit(bool wait_completion) {
// End the current task recording.
auto& task = tasks[current_task];
task.command_buffer.end();
@@ -114,24 +122,12 @@ void VKTaskScheduler::Submit(bool present, bool wait_completion) {
// When the task completes the timeline will increment to the task id
vk::TimelineSemaphoreSubmitInfo timeline_info({}, task.task_id);
std::array<vk::Semaphore, 2> signal_semaphores = { timeline.get(), present_semaphore.get() };
vk::PipelineStageFlags wait_stage = vk::PipelineStageFlagBits::eColorAttachmentOutput;
vk::SubmitInfo submit_info({}, wait_stage, task.command_buffer, signal_semaphores, &timeline_info);
// Wait for new swapchain image
if (present) {
auto available = swapchain->AcquireNextImage();
submit_info.setWaitSemaphores(available);
}
vk::SubmitInfo submit_info({}, wait_stage, task.command_buffer, timeline.get(), &timeline_info);
// Submit the command buffer
g_vk_instace->GetGraphicsQueue().submit(submit_info);
// Present the image when rendering has finished
if (present) {
swapchain->Present(present_semaphore.get());
}
// Block host until the GPU catches up
if (wait_completion) {
SyncToGPU();

View File

@@ -33,16 +33,18 @@ class VKSwapChain;
/// frame latency if the CPU is too far ahead of the GPU
class VKTaskScheduler {
public:
explicit VKTaskScheduler(VKSwapChain* swapchain);
VKTaskScheduler() = default;
~VKTaskScheduler();
/// Create and initialize the work scheduler
bool Create();
/// Retrieve either of the current frame's command buffers
vk::CommandBuffer GetCommandBuffer() const { return tasks[current_task].command_buffer; }
VKBuffer& GetStaging() { return tasks[current_task].staging; }
vk::CommandBuffer GetCommandBuffer() const;
vk::DescriptorSet GetDescriptorSet(u32 index) const;
std::tuple<u8*, u32> RequestStaging(u32 size);
VKBuffer& GetStaging() { return tasks[current_task].staging; }
/// Returns the task id that the CPU is recording
u64 GetCPUTick() const { return current_task_id; }
@@ -58,18 +60,19 @@ public:
void Schedule(std::function<void()> func);
/// Submit the current work batch and move to the next frame
void Submit(bool present = true, bool wait_completion = false);
void Submit(bool wait_completion = false);
private:
void BeginTask();
private:
struct Task {
u64 task_id{};
std::vector<std::function<void()>> cleanups;
vk::CommandBuffer command_buffer;
VKBuffer staging;
u32 current_offset{};
u64 current_offset{}, task_id{};
vk::CommandBuffer command_buffer;
std::vector<vk::UniqueDescriptorSet> descriptor_sets;
vk::UniqueDescriptorPool desc_pool;
std::vector<std::function<void()>> cleanups;
};
vk::UniqueSemaphore timeline;
@@ -78,11 +81,7 @@ private:
// Each task contains unique resources
std::array<Task, CONCURRENT_TASK_COUNT> tasks;
u32 current_task = CONCURRENT_TASK_COUNT - 1;
// Presentation semaphore
vk::UniqueSemaphore present_semaphore;
VKSwapChain* swapchain = nullptr;
u64 current_task = 0;
};
extern std::unique_ptr<VKTaskScheduler> g_vk_task_scheduler;

View File

@@ -10,49 +10,31 @@
namespace Vulkan {
VKTexture::~VKTexture() {
if (texture) {
// Make sure to unbind the texture before destroying it
auto& state = VulkanState::Get();
state.UnbindTexture(this);
auto deleter = [this]() {
auto& device = g_vk_instace->GetDevice();
device.destroyImage(texture);
device.destroyImageView(view);
device.freeMemory(memory);
};
// Schedule deletion of the texture after it's no longer used
// by the GPU
g_vk_task_scheduler->Schedule(deleter);
static int BytesPerPixel(vk::Format format) {
switch (format) {
case vk::Format::eR8G8B8A8Uint:
return 4;
case vk::Format::eR8G8B8Uint:
return 3;
case vk::Format::eR5G6B5UnormPack16:
case vk::Format::eR5G5B5A1UnormPack16:
case vk::Format::eR4G4B4A4UnormPack16:
return 2;
default:
UNREACHABLE();
}
}
VKTexture& VKTexture::operator=(VKTexture&& move) {
VKTexture::~VKTexture() {
Destroy();
}
void VKTexture::Create(const VKTexture::Info& create_info) {
auto& device = g_vk_instace->GetDevice();
info = create_info;
switch (info.format)
{
case vk::Format::eR8G8B8A8Uint:
case vk::Format::eR8G8B8A8Srgb:
case vk::Format::eR32Uint:
channels = 4;
break;
case vk::Format::eR8G8B8Uint:
channels = 3;
break;
default:
LOG_CRITICAL(Render_Vulkan, "Unknown texture format {}", info.format);
}
// Create the texture
image_size = info.width * info.height * channels;
image_size = info.width * info.height * BytesPerPixel(info.format);
vk::ImageCreateFlags flags{};
if (info.view_type == vk::ImageViewType::eCube) {
@@ -71,7 +53,8 @@ void VKTexture::Create(const VKTexture::Info& create_info) {
// Create texture memory
auto requirements = device.getImageMemoryRequirements(texture);
auto memory_index = VKBuffer::FindMemoryType(requirements.memoryTypeBits, vk::MemoryPropertyFlagBits::eDeviceLocal);
auto memory_index = VKBuffer::FindMemoryType(requirements.memoryTypeBits,
vk::MemoryPropertyFlagBits::eDeviceLocal);
vk::MemoryAllocateInfo alloc_info(requirements.size, memory_index);
memory = device.allocateMemory(alloc_info);
@@ -86,6 +69,25 @@ void VKTexture::Create(const VKTexture::Info& create_info) {
view = device.createImageView(view_info);
}
void VKTexture::Destroy() {
if (texture) {
// Make sure to unbind the texture before destroying it
auto& state = VulkanState::Get();
state.UnbindTexture(*this);
auto deleter = [this]() {
auto& device = g_vk_instace->GetDevice();
device.destroyImage(texture);
device.destroyImageView(view);
device.freeMemory(memory);
};
// Schedule deletion of the texture after it's no longer used
// by the GPU
g_vk_task_scheduler->Schedule(deleter);
}
}
void VKTexture::Transition(vk::ImageLayout new_layout) {
if (new_layout == layout) {
return;
@@ -161,9 +163,8 @@ void VKTexture::Transition(vk::ImageLayout new_layout) {
vk::ImageSubresourceRange(info.aspect, 0, 1, 0, 1)
};
std::array<vk::ImageMemoryBarrier, 1> barriers{ barrier };
auto command_buffer = g_vk_task_scheduler->GetCommandBuffer();
command_buffer.pipelineBarrier(source.stage, dst.stage, vk::DependencyFlagBits::eByRegion, {}, {}, barriers);
command_buffer.pipelineBarrier(source.stage, dst.stage, vk::DependencyFlagBits::eByRegion, {}, {}, barrier);
layout = new_layout;
}
@@ -173,9 +174,8 @@ void VKTexture::Upload(u32 level, u32 layer, u32 row_length, vk::Rect2D region,
LOG_ERROR(Render_Vulkan, "Cannot upload pixels without staging buffer!");
}
auto command_buffer = g_vk_task_scheduler->GetCommandBuffer();
// Copy pixels to staging buffer
auto command_buffer = g_vk_task_scheduler->GetCommandBuffer();
std::memcpy(buffer, pixels.data(), pixels.size());
vk::BufferImageCopy copy_region{
@@ -185,6 +185,10 @@ void VKTexture::Upload(u32 level, u32 layer, u32 row_length, vk::Rect2D region,
{region.extent.width, region.extent.height, 1}
};
// Exit rendering for transfer operations
auto& state = VulkanState::Get();
state.EndRendering();
// Transition image to transfer format
auto old_layout = GetLayout();
Transition(vk::ImageLayout::eTransferDstOptimal);
@@ -213,6 +217,10 @@ void VKTexture::Download(u32 level, u32 layer, u32 row_length, vk::Rect2D region
{region.extent.width, region.extent.height, 1}
};
// Exit rendering for transfer operations
auto& state = VulkanState::Get();
state.EndRendering();
// Transition image to transfer format
auto old_layout = GetLayout();
Transition(vk::ImageLayout::eTransferSrcOptimal);
@@ -223,7 +231,7 @@ void VKTexture::Download(u32 level, u32 layer, u32 row_length, vk::Rect2D region
// Wait for the data to be available
// NOTE: This is really slow and should be reworked
g_vk_task_scheduler->Submit(false, true);
g_vk_task_scheduler->Submit(true);
std::memcpy(memory.data(), buffer, memory.size_bytes());
// Restore layout

View File

@@ -14,12 +14,6 @@
namespace Vulkan {
struct SamplerInfo {
std::array<vk::SamplerAddressMode, 3> wrapping{};
vk::Filter min_filter{}, mag_filter{};
vk::SamplerMipmapMode mipmap_mode{};
};
/// Vulkan texture object
class VKTexture final : public NonCopyable {
public:
@@ -33,7 +27,6 @@ public:
vk::ImageAspectFlags aspect;
u32 multisamples = 1;
u32 levels = 1, layers = 1;
SamplerInfo sampler_info = {};
};
VKTexture() = default;
@@ -44,6 +37,8 @@ public:
/// Create a new Vulkan texture object
void Create(const VKTexture::Info& info);
void Adopt
void Destroy();
/// Query objects
bool IsValid() const { return texture; }
@@ -68,7 +63,8 @@ private:
vk::Image texture;
vk::ImageView view;
vk::DeviceMemory memory;
u32 channels{}, image_size{};
u32 image_size{};
bool adopted = false;
};
} // namespace Vulkan