Refactor Vulkan state and pipeline builders
This commit is contained in:
@@ -32,6 +32,7 @@
|
|||||||
#include "video_core/renderer_vulkan/vk_state.h"
|
#include "video_core/renderer_vulkan/vk_state.h"
|
||||||
#include "video_core/renderer_vulkan/renderer_vulkan.h"
|
#include "video_core/renderer_vulkan/renderer_vulkan.h"
|
||||||
#include "video_core/renderer_vulkan/vk_task_scheduler.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 "video_core/video_core.h"
|
||||||
|
|
||||||
// Include these late to avoid polluting previous headers
|
// Include these late to avoid polluting previous headers
|
||||||
@@ -129,17 +130,17 @@ std::vector<const char*> RequiredExtensions(Frontend::WindowSystemType window_ty
|
|||||||
return extensions;
|
return extensions;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char vertex_shader[] = R"(
|
static const char vertex_shader_source[] = R"(
|
||||||
in vec2 vert_position;
|
layout (location = 0) in vec2 vert_position;
|
||||||
in vec2 vert_tex_coord;
|
layout (location = 1) in vec2 vert_tex_coord;
|
||||||
out vec2 frag_tex_coord;
|
layout (location = 0) out vec2 frag_tex_coord;
|
||||||
|
|
||||||
// This is a truncated 3x3 matrix for 2D transformations:
|
// This is a truncated 3x3 matrix for 2D transformations:
|
||||||
// The upper-left 2x2 submatrix performs scaling/rotation/mirroring.
|
// The upper-left 2x2 submatrix performs scaling/rotation/mirroring.
|
||||||
// The third column performs translation.
|
// 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
|
// 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]
|
// implicitly be [0, 0, 1]
|
||||||
uniform mat3x2 modelview_matrix;
|
layout (push_constant) uniform mat3x2 modelview_matrix;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
// Multiply input position by the rotscale part of the matrix and then manually translate by
|
// 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"(
|
static const char fragment_shader_source[] = R"(
|
||||||
in vec2 frag_tex_coord;
|
layout (location = 0) in vec2 frag_tex_coord;
|
||||||
layout(location = 0) out vec4 color;
|
layout (location = 0) out vec4 color;
|
||||||
|
|
||||||
uniform vec4 i_resolution;
|
layout (push_constant) uniform DrawInfo {
|
||||||
uniform vec4 o_resolution;
|
vec4 i_resolution;
|
||||||
uniform int layer;
|
vec4 o_resolution;
|
||||||
|
int layer;
|
||||||
|
};
|
||||||
|
|
||||||
uniform sampler2D color_texture;
|
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.
|
* Vertex structure that the drawn screen rectangles are composed of.
|
||||||
*/
|
*/
|
||||||
struct ScreenRectVertex {
|
|
||||||
ScreenRectVertex(GLfloat x, GLfloat y, GLfloat u, GLfloat v) {
|
struct ScreenRectVertexBase {
|
||||||
position[0] = x;
|
ScreenRectVertexBase() = default;
|
||||||
position[1] = y;
|
ScreenRectVertexBase(float x, float y, float u, float v) {
|
||||||
tex_coord[0] = u;
|
position.x = x;
|
||||||
tex_coord[1] = v;
|
position.y = y;
|
||||||
|
tex_coord.x = u;
|
||||||
|
tex_coord.y = v;
|
||||||
}
|
}
|
||||||
|
|
||||||
GLfloat position[2];
|
glm::vec2 position;
|
||||||
GLfloat tex_coord[2];
|
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)
|
/// Swap buffers (render frame)
|
||||||
void RendererVulkan::SwapBuffers() {
|
void RendererVulkan::SwapBuffers() {
|
||||||
// Maintain the rasterizer's state as a priority
|
|
||||||
OpenGLState prev_state = OpenGLState::GetCurState();
|
|
||||||
state.Apply();
|
|
||||||
|
|
||||||
PrepareRendertarget();
|
PrepareRendertarget();
|
||||||
|
|
||||||
const auto& layout = render_window.GetFramebufferLayout();
|
const auto& layout = render_window.GetFramebufferLayout();
|
||||||
RenderToMailbox(layout, render_window.mailbox, false);
|
DrawScreens(layout, false);
|
||||||
|
|
||||||
m_current_frame++;
|
m_current_frame++;
|
||||||
|
|
||||||
@@ -292,7 +253,6 @@ void RendererVulkan::SwapBuffers() {
|
|||||||
Core::System::GetInstance().CoreTiming().GetGlobalTimeUs());
|
Core::System::GetInstance().CoreTiming().GetGlobalTimeUs());
|
||||||
Core::System::GetInstance().perf_stats->BeginSystemFrame();
|
Core::System::GetInstance().perf_stats->BeginSystemFrame();
|
||||||
|
|
||||||
prev_state.Apply();
|
|
||||||
RefreshRasterizerSetting();
|
RefreshRasterizerSetting();
|
||||||
|
|
||||||
if (Pica::g_debug_context && Pica::g_debug_context->recorder) {
|
if (Pica::g_debug_context && Pica::g_debug_context->recorder) {
|
||||||
@@ -315,9 +275,9 @@ void RendererVulkan::PrepareRendertarget() {
|
|||||||
if (color_fill.is_enabled) {
|
if (color_fill.is_enabled) {
|
||||||
LoadColorToActiveGLTexture(color_fill.color_r, color_fill.color_g, color_fill.color_b, screen_infos[i]);
|
LoadColorToActiveGLTexture(color_fill.color_r, color_fill.color_g, color_fill.color_b, screen_infos[i]);
|
||||||
} else {
|
} else {
|
||||||
auto rect = screen_infos[i].texture->GetExtent();
|
auto extent = screen_infos[i].texture.GetArea().extent;
|
||||||
auto format = screen_infos[i].format;
|
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) {
|
format != framebuffer.color_format) {
|
||||||
// Reallocate texture if the framebuffer size has changed.
|
// Reallocate texture if the framebuffer size has changed.
|
||||||
// This is expected to not happen very often and hence should not be a
|
// 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);
|
LoadFBToScreenInfo(framebuffer, screen_infos[i], i == 1);
|
||||||
|
|
||||||
// Resize the texture in case the framebuffer size has changed
|
// Resize the texture in case the framebuffer size has changed
|
||||||
screen_infos[i].texture.width = framebuffer.width;
|
//screen_infos[i].texture.width = framebuffer.width;
|
||||||
screen_infos[i].texture.height = framebuffer.height;
|
//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.
|
* 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)) {
|
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
|
// 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);
|
screen_info.display_texcoords = Common::Rectangle<float>(0.f, 0.f, 1.f, 1.f);
|
||||||
|
|
||||||
Memory::RasterizerFlushRegion(framebuffer_addr, framebuffer.stride * framebuffer.height);
|
Memory::RasterizerFlushRegion(framebuffer_addr, framebuffer.stride * framebuffer.height);
|
||||||
|
|
||||||
vk::Rect2D region{{0, 0}, {framebuffer.width, framebuffer.height}};
|
vk::Rect2D region{{0, 0}, {framebuffer.width, framebuffer.height}};
|
||||||
std::span<u8> framebuffer_data(VideoCore::g_memory->GetPhysicalPointer(framebuffer_addr),
|
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
|
* 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.
|
* be 1x1 but will stretch across whatever it's rendered on.
|
||||||
*/
|
*/
|
||||||
void RendererVulkan::LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b,
|
void RendererVulkan::LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b, const ScreenInfo& screen) {
|
||||||
const ScreenInfo& screen) {
|
/*state.texture_units[0].texture_2d = texture.resource.handle;
|
||||||
state.texture_units[0].texture_2d = texture.resource.handle;
|
|
||||||
state.Apply();
|
state.Apply();
|
||||||
|
|
||||||
glActiveTexture(GL_TEXTURE0);
|
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);
|
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.texture_units[0].texture_2d = 0;
|
||||||
state.Apply();
|
state.Apply();*/
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the OpenGL state and creates persistent objects.
|
* 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,
|
glClearColor(Settings::values.bg_red, Settings::values.bg_green, Settings::values.bg_blue,
|
||||||
0.0f);
|
0.0f);
|
||||||
|
|
||||||
filter_sampler.Create();
|
//filter_sampler.Create();
|
||||||
ReloadSampler();
|
//ReloadSampler();
|
||||||
|
|
||||||
ReloadShader();
|
ReloadShader();
|
||||||
|
|
||||||
// Generate VBO handle for drawing
|
// 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
|
void RendererVulkan::ConfigureRenderPipeline() {
|
||||||
vertex_array.Create();
|
// 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;
|
auto& device = g_vk_instace->GetDevice();
|
||||||
state.draw.vertex_buffer = vertex_buffer.handle;
|
descriptor_layout = device.createDescriptorSetLayoutUnique(color_texture_info);
|
||||||
state.draw.uniform_buffer = 0;
|
|
||||||
state.Apply();
|
|
||||||
|
|
||||||
// Attach vertex data to VAO
|
// Build the display pipeline layout
|
||||||
glBufferData(GL_ARRAY_BUFFER, sizeof(ScreenRectVertex) * 4, nullptr, GL_STREAM_DRAW);
|
PipelineLayoutBuilder lbuilder;
|
||||||
glVertexAttribPointer(attrib_position, 2, GL_FLOAT, GL_FALSE, sizeof(ScreenRectVertex),
|
lbuilder.AddDescriptorSet(descriptor_layout.get());
|
||||||
(GLvoid*)offsetof(ScreenRectVertex, position));
|
lbuilder.AddPushConstants(vk::ShaderStageFlagBits::eVertex, 0, sizeof(glm::mat2x3));
|
||||||
glVertexAttribPointer(attrib_tex_coord, 2, GL_FLOAT, GL_FALSE, sizeof(ScreenRectVertex),
|
lbuilder.AddPushConstants(vk::ShaderStageFlagBits::eFragment, 0, sizeof(DrawInfo));
|
||||||
(GLvoid*)offsetof(ScreenRectVertex, tex_coord));
|
pipeline_layout = vk::UniquePipelineLayout{lbuilder.Build()};
|
||||||
glEnableVertexAttribArray(attrib_position);
|
|
||||||
glEnableVertexAttribArray(attrib_tex_coord);
|
|
||||||
|
|
||||||
// Allocate textures for each screen
|
std::array<vk::DynamicState, 3> dynamic_states{
|
||||||
for (auto& screen_info : screen_infos) {
|
vk::DynamicState::eLineWidth, vk::DynamicState::eViewport, vk::DynamicState::eScissor,
|
||||||
screen_info.texture.resource.Create();
|
};
|
||||||
|
|
||||||
// Allocation of storage is deferred until the first frame, when we
|
// Build the display pipeline
|
||||||
// know the framebuffer size.
|
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;
|
// Configure vertex buffer
|
||||||
state.Apply();
|
auto attributes = ScreenRectVertex::attribute_desc;
|
||||||
|
builder.AddVertexBuffer(0, sizeof(ScreenRectVertex), vk::VertexInputRate::eVertex, attributes);
|
||||||
|
|
||||||
glActiveTexture(GL_TEXTURE0);
|
pipeline = vk::UniquePipeline{builder.Build()};
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RendererVulkan::ReloadSampler() {
|
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);
|
Settings::values.filter_mode ? GL_LINEAR : GL_NEAREST);
|
||||||
glSamplerParameteri(filter_sampler.handle, GL_TEXTURE_MAG_FILTER,
|
glSamplerParameteri(filter_sampler.handle, GL_TEXTURE_MAG_FILTER,
|
||||||
Settings::values.filter_mode ? GL_LINEAR : GL_NEAREST);
|
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_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() {
|
void RendererVulkan::ReloadShader() {
|
||||||
// Link shaders and get variable locations
|
// Link shaders and get variable locations
|
||||||
std::string shader_data = fragment_shader;
|
vertex_shader = vk::UniqueShaderModule{VulkanState::CompileShader(vertex_shader_source,
|
||||||
/*if (GLES) {
|
vk::ShaderStageFlagBits::eVertex)};
|
||||||
shader_data += fragment_shader_precision_OES;
|
fragment_shader = vk::UniqueShaderModule{VulkanState::CompileShader(fragment_shader_source,
|
||||||
}
|
vk::ShaderStageFlagBits::eFragment)};
|
||||||
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");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RendererVulkan::ConfigureFramebufferTexture(TextureInfo& texture,
|
void RendererVulkan::ConfigureFramebufferTexture(ScreenInfo& screen, const GPU::Regs::FramebufferConfig& framebuffer) {
|
||||||
const GPU::Regs::FramebufferConfig& framebuffer) {
|
|
||||||
GPU::Regs::PixelFormat format = framebuffer.color_format;
|
GPU::Regs::PixelFormat format = framebuffer.color_format;
|
||||||
GLint internal_format;
|
|
||||||
|
|
||||||
texture.format = format;
|
VKTexture::Info texture_info{
|
||||||
texture.width = framebuffer.width;
|
.width = framebuffer.width,
|
||||||
texture.height = framebuffer.height;
|
.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) {
|
switch (format) {
|
||||||
case GPU::Regs::PixelFormat::RGBA8:
|
case GPU::Regs::PixelFormat::RGBA8:
|
||||||
internal_format = GL_RGBA;
|
texture_info.format = vk::Format::eR8G8B8A8Unorm;
|
||||||
texture.gl_format = GL_RGBA;
|
|
||||||
texture.gl_type = GLES ? GL_UNSIGNED_BYTE : GL_UNSIGNED_INT_8_8_8_8;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case GPU::Regs::PixelFormat::RGB8:
|
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
|
// specific OpenGL type used in this function using native-endian (that is, little-endian
|
||||||
// mostly everywhere) for words or half-words.
|
// mostly everywhere) for words or half-words.
|
||||||
// TODO: check how those behave on big-endian processors.
|
// 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
|
// GLES Dosen't support BGR , Use RGB instead
|
||||||
texture.gl_format = GLES ? GL_RGB : GL_BGR;
|
//texture.gl_format = GLES ? GL_RGB : GL_BGR;
|
||||||
texture.gl_type = GL_UNSIGNED_BYTE;
|
//texture.gl_type = GL_UNSIGNED_BYTE;
|
||||||
|
texture_info.format = vk::Format::eR8G8B8Unorm;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case GPU::Regs::PixelFormat::RGB565:
|
case GPU::Regs::PixelFormat::RGB565:
|
||||||
internal_format = GL_RGB;
|
texture_info.format = vk::Format::eR5G6B5UnormPack16;
|
||||||
texture.gl_format = GL_RGB;
|
|
||||||
texture.gl_type = GL_UNSIGNED_SHORT_5_6_5;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case GPU::Regs::PixelFormat::RGB5A1:
|
case GPU::Regs::PixelFormat::RGB5A1:
|
||||||
internal_format = GL_RGBA;
|
texture_info.format = vk::Format::eR5G5B5A1UnormPack16;
|
||||||
texture.gl_format = GL_RGBA;
|
|
||||||
texture.gl_type = GL_UNSIGNED_SHORT_5_5_5_1;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case GPU::Regs::PixelFormat::RGBA4:
|
case GPU::Regs::PixelFormat::RGBA4:
|
||||||
internal_format = GL_RGBA;
|
texture_info.format = vk::Format::eR4G4B4A4UnormPack16;
|
||||||
texture.gl_format = GL_RGBA;
|
|
||||||
texture.gl_type = GL_UNSIGNED_SHORT_4_4_4_4;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
UNIMPLEMENTED();
|
UNIMPLEMENTED();
|
||||||
}
|
}
|
||||||
|
|
||||||
state.texture_units[0].texture_2d = texture.resource.handle;
|
auto& texture = screen.texture;
|
||||||
state.Apply();
|
texture.Destroy();
|
||||||
|
texture.Create(texture_info);
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -638,15 +502,21 @@ void RendererVulkan::DrawSingleScreenRotated(const ScreenInfo& screen_info, floa
|
|||||||
ScreenRectVertex(x + w, y + h, texcoords.top, texcoords.right),
|
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
|
// 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
|
// 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.
|
// should probably be set to the standard (w, h, 1.0 / w, 1.0 / h) ordering.
|
||||||
const u16 scale_factor = VideoCore::GetResolutionScaleFactor();
|
const u16 scale_factor = VideoCore::GetResolutionScaleFactor();
|
||||||
glUniform4f(uniform_i_resolution, static_cast<float>(screen_info.texture.width * scale_factor),
|
auto [width, height] = screen_info.texture.GetArea().extent;
|
||||||
static_cast<float>(screen_info.texture.height * scale_factor),
|
|
||||||
1.0f / static_cast<float>(screen_info.texture.width * scale_factor),
|
draw_info.i_resolution = glm::vec4(width * scale_factor, height * scale_factor,
|
||||||
1.0f / static_cast<float>(screen_info.texture.height * scale_factor));
|
1.0f / (width * scale_factor),
|
||||||
glUniform4f(uniform_o_resolution, h, w, 1.0f / h, 1.0f / w);
|
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].texture_2d = screen_info.display_texture;
|
||||||
state.texture_units[0].sampler = filter_sampler.handle;
|
state.texture_units[0].sampler = filter_sampler.handle;
|
||||||
state.Apply();
|
state.Apply();
|
||||||
@@ -776,12 +646,12 @@ void RendererVulkan::DrawScreens(const Layout::FramebufferLayout& layout, bool f
|
|||||||
ReloadSampler();
|
ReloadSampler();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (VideoCore::g_renderer_shader_update_requested.exchange(false)) {
|
/*if (VideoCore::g_renderer_shader_update_requested.exchange(false)) {
|
||||||
// Update fragment shader before drawing
|
// Update fragment shader before drawing
|
||||||
shader.Release();
|
shader.Release();
|
||||||
// Link shaders and get variable locations
|
// Link shaders and get variable locations
|
||||||
ReloadShader();
|
ReloadShader();
|
||||||
}
|
}*/
|
||||||
|
|
||||||
const auto& top_screen = layout.top_screen;
|
const auto& top_screen = layout.top_screen;
|
||||||
const auto& bottom_screen = layout.bottom_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();
|
// Swap chain images start in undefined
|
||||||
auto frame = render_window.mailbox->TryGetPresentFrame(timeout_ms);
|
Vulkan::Texture& swap_chain_texture = m_swap_chain->GetCurrentTexture();
|
||||||
if (!frame) {
|
swap_chain_texture.OverrideImageLayout(VK_IMAGE_LAYOUT_UNDEFINED);
|
||||||
LOG_DEBUG(Render_Vulkan, "TryGetPresentFrame returned no frame to present");
|
swap_chain_texture.TransitionToLayout(cmdbuffer, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clearing before a full overwrite of a fbo can signal to drivers that they can avoid a
|
const VkClearValue clear_value = {{{0.0f, 0.0f, 0.0f, 1.0f}}};
|
||||||
// readback since we won't be doing any blending
|
const VkRenderPassBeginInfo rp = {VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, nullptr, m_swap_chain->GetClearRenderPass(),
|
||||||
glClear(GL_COLOR_BUFFER_BIT);
|
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
|
const VkViewport vp{
|
||||||
if (frame->color_reloaded) {
|
0.0f, 0.0f, static_cast<float>(swap_chain_texture.GetWidth()), static_cast<float>(swap_chain_texture.GetHeight()), 0.0f, 1.0f};
|
||||||
LOG_DEBUG(Render_Vulkan, "Reloading present frame");
|
const VkRect2D scissor{{0, 0}, {static_cast<u32>(swap_chain_texture.GetWidth()), static_cast<u32>(swap_chain_texture.GetHeight())}};
|
||||||
render_window.mailbox->ReloadPresentFrame(frame, layout.width, layout.height);
|
vkCmdSetViewport(g_vulkan_context->GetCurrentCommandBuffer(), 0, 1, &vp);
|
||||||
}
|
vkCmdSetScissor(g_vulkan_context->GetCurrentCommandBuffer(), 0, 1, &scissor);
|
||||||
|
return true;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Updates the framerate
|
void RendererVulkan::EndPresent() {
|
||||||
void RendererVulkan::UpdateFramerate() {}
|
ImGui::Render();
|
||||||
|
ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData());
|
||||||
|
|
||||||
static const char* GetSource(GLenum source) {
|
VkCommandBuffer cmdbuffer = g_vulkan_context->GetCurrentCommandBuffer();
|
||||||
#define RET(s) \
|
vkCmdEndRenderPass(g_vulkan_context->GetCurrentCommandBuffer());
|
||||||
case GL_DEBUG_SOURCE_##s: \
|
m_swap_chain->GetCurrentTexture().TransitionToLayout(cmdbuffer, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
|
||||||
return #s
|
|
||||||
switch (source) {
|
|
||||||
RET(API);
|
|
||||||
RET(WINDOW_SYSTEM);
|
|
||||||
RET(SHADER_COMPILER);
|
|
||||||
RET(THIRD_PARTY);
|
|
||||||
RET(APPLICATION);
|
|
||||||
RET(OTHER);
|
|
||||||
default:
|
|
||||||
UNREACHABLE();
|
|
||||||
}
|
|
||||||
#undef RET
|
|
||||||
}
|
|
||||||
|
|
||||||
static const char* GetType(GLenum type) {
|
g_vulkan_context->SubmitCommandBuffer(m_swap_chain->GetImageAvailableSemaphore(), m_swap_chain->GetRenderingFinishedSemaphore(),
|
||||||
#define RET(t) \
|
m_swap_chain->GetSwapChain(), m_swap_chain->GetCurrentImageIndex(), !m_swap_chain->IsPresentModeSynchronizing());
|
||||||
case GL_DEBUG_TYPE_##t: \
|
g_vulkan_context->MoveToNextCommandBuffer();
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initialize the renderer
|
/// Initialize the renderer
|
||||||
@@ -1050,14 +857,19 @@ VideoCore::ResultStatus RendererVulkan::Init() {
|
|||||||
g_vk_instace = std::make_unique<VKInstance>();
|
g_vk_instace = std::make_unique<VKInstance>();
|
||||||
g_vk_instace->Create(instance, physical_device, surface, true);
|
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();
|
auto& telemetry_session = Core::System::GetInstance().TelemetrySession();
|
||||||
constexpr auto user_system = Common::Telemetry::FieldType::UserSystem;
|
constexpr auto user_system = Common::Telemetry::FieldType::UserSystem;
|
||||||
telemetry_session.AddField(user_system, "GPU_Vendor", "NVIDIA");
|
telemetry_session.AddField(user_system, "GPU_Vendor", "NVIDIA");
|
||||||
telemetry_session.AddField(user_system, "GPU_Model", "GTX 1650");
|
telemetry_session.AddField(user_system, "GPU_Model", "GTX 1650");
|
||||||
telemetry_session.AddField(user_system, "GPU_Vulkan_Version", "Vulkan 1.3");
|
telemetry_session.AddField(user_system, "GPU_Vulkan_Version", "Vulkan 1.3");
|
||||||
|
|
||||||
InitOpenGLObjects();
|
// Initialize the renderer
|
||||||
|
CreateVulkanObjects();
|
||||||
RefreshRasterizerSetting();
|
RefreshRasterizerSetting();
|
||||||
|
|
||||||
return VideoCore::ResultStatus::Success;
|
return VideoCore::ResultStatus::Success;
|
||||||
|
@@ -30,12 +30,18 @@ namespace Vulkan {
|
|||||||
|
|
||||||
/// Structure used for storing information about the display target for each 3DS screen
|
/// Structure used for storing information about the display target for each 3DS screen
|
||||||
struct ScreenInfo {
|
struct ScreenInfo {
|
||||||
Vulkan::VKTexture* display_texture;
|
vk::Image display_texture;
|
||||||
Common::Rectangle<float> display_texcoords;
|
Common::Rectangle<float> display_texcoords;
|
||||||
Vulkan::VKTexture* texture;
|
Vulkan::VKTexture texture;
|
||||||
GPU::Regs::PixelFormat format;
|
GPU::Regs::PixelFormat format;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct DrawInfo {
|
||||||
|
glm::vec4 i_resolution;
|
||||||
|
glm::vec4 o_resolution;
|
||||||
|
int layer;
|
||||||
|
};
|
||||||
|
|
||||||
class RendererVulkan : public RendererBase {
|
class RendererVulkan : public RendererBase {
|
||||||
public:
|
public:
|
||||||
RendererVulkan(Frontend::EmuWindow& window);
|
RendererVulkan(Frontend::EmuWindow& window);
|
||||||
@@ -47,29 +53,25 @@ public:
|
|||||||
/// Shutdown the renderer
|
/// Shutdown the renderer
|
||||||
void ShutDown() override;
|
void ShutDown() override;
|
||||||
|
|
||||||
/// Finalizes rendering the guest frame
|
bool BeginPresent();
|
||||||
void SwapBuffers() override;
|
void EndPresent();
|
||||||
|
|
||||||
/// Draws the latest frame from texture mailbox to the currently bound draw framebuffer in this
|
|
||||||
/// context
|
|
||||||
void TryPresent(int timeout_ms) override;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void InitOpenGLObjects();
|
void CreateVulkanObjects();
|
||||||
|
void ConfigureRenderPipeline();
|
||||||
void ReloadSampler();
|
void ReloadSampler();
|
||||||
void ReloadShader();
|
void ReloadShader();
|
||||||
void PrepareRendertarget();
|
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 ConfigureFramebufferTexture(ScreenInfo& screen, const GPU::Regs::FramebufferConfig& framebuffer);
|
||||||
|
|
||||||
void DrawScreens(const Layout::FramebufferLayout& layout, bool flipped);
|
void DrawScreens(const Layout::FramebufferLayout& layout, bool flipped);
|
||||||
void DrawSingleScreenRotated(const ScreenInfo& screen_info, float x, float y, float w, float h);
|
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 DrawSingleScreen(const ScreenInfo& screen_info, float x, float y, float w, float h);
|
||||||
void DrawSingleScreenStereoRotated(const ScreenInfo& screen_info_l,
|
void DrawSingleScreenStereoRotated(const ScreenInfo& screen_info_l,
|
||||||
const ScreenInfo& screen_info_r, float x, float y, float w,
|
const ScreenInfo& screen_info_r, float x, float y, float w, float h);
|
||||||
float h);
|
|
||||||
void DrawSingleScreenStereo(const ScreenInfo& screen_info_l, const ScreenInfo& screen_info_r,
|
void DrawSingleScreenStereo(const ScreenInfo& screen_info_l, const ScreenInfo& screen_info_r,
|
||||||
float x, float y, float w, float h);
|
float x, float y, float w, float h);
|
||||||
|
|
||||||
void UpdateFramerate();
|
void UpdateFramerate();
|
||||||
|
|
||||||
// Loads framebuffer from emulated memory into the display information structure
|
// Loads framebuffer from emulated memory into the display information structure
|
||||||
@@ -78,12 +80,18 @@ private:
|
|||||||
// Fills active OpenGL texture with the given RGB color.
|
// Fills active OpenGL texture with the given RGB color.
|
||||||
void LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b, const ScreenInfo& screen);
|
void LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b, const ScreenInfo& screen);
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Vulkan state
|
||||||
|
DrawInfo draw_info{};
|
||||||
VulkanState state;
|
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;
|
VKBuffer vertex_buffer;
|
||||||
//OGLProgram shader;
|
|
||||||
//OGLSampler filter_sampler;
|
|
||||||
|
|
||||||
/// Display information for top and bottom screens respectively
|
/// Display information for top and bottom screens respectively
|
||||||
std::array<ScreenInfo, 3> screen_infos;
|
std::array<ScreenInfo, 3> screen_infos;
|
||||||
|
@@ -12,19 +12,7 @@
|
|||||||
namespace Vulkan {
|
namespace Vulkan {
|
||||||
|
|
||||||
VKBuffer::~VKBuffer() {
|
VKBuffer::~VKBuffer() {
|
||||||
if (buffer) {
|
Destroy();
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void VKBuffer::Create(const VKBuffer::Info& info) {
|
void VKBuffer::Create(const VKBuffer::Info& info) {
|
||||||
@@ -46,6 +34,32 @@ void VKBuffer::Create(const VKBuffer::Info& info) {
|
|||||||
if (info.properties & vk::MemoryPropertyFlagBits::eHostVisible) {
|
if (info.properties & vk::MemoryPropertyFlagBits::eHostVisible) {
|
||||||
memory = device.mapMemory(buffer_memory, 0, info.size);
|
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) {
|
void VKBuffer::CopyBuffer(VKBuffer* src_buffer, VKBuffer* dst_buffer, vk::BufferCopy region) {
|
||||||
|
@@ -7,11 +7,14 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <deque>
|
#include <deque>
|
||||||
|
#include <span>
|
||||||
#include <vulkan/vulkan.hpp>
|
#include <vulkan/vulkan.hpp>
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
|
|
||||||
namespace Vulkan {
|
namespace Vulkan {
|
||||||
|
|
||||||
|
constexpr u32 MAX_BUFFER_VIEWS = 5;
|
||||||
|
|
||||||
/// Generic Vulkan buffer object used by almost every resource
|
/// Generic Vulkan buffer object used by almost every resource
|
||||||
class VKBuffer final : public NonCopyable {
|
class VKBuffer final : public NonCopyable {
|
||||||
public:
|
public:
|
||||||
@@ -19,22 +22,27 @@ public:
|
|||||||
u32 size;
|
u32 size;
|
||||||
vk::MemoryPropertyFlags properties;
|
vk::MemoryPropertyFlags properties;
|
||||||
vk::BufferUsageFlags usage;
|
vk::BufferUsageFlags usage;
|
||||||
|
std::array<vk::Format, MAX_BUFFER_VIEWS> view_formats{};
|
||||||
};
|
};
|
||||||
|
|
||||||
VKBuffer() = default;
|
VKBuffer() = default;
|
||||||
VKBuffer(VKBuffer&&) = default;
|
|
||||||
~VKBuffer();
|
~VKBuffer();
|
||||||
|
|
||||||
|
VKBuffer(VKBuffer&&) = default;
|
||||||
|
VKBuffer& operator=(VKBuffer&&) = default;
|
||||||
|
|
||||||
/// Create a new Vulkan buffer object
|
/// Create a new Vulkan buffer object
|
||||||
void Create(const Info& info);
|
void Create(const Info& info);
|
||||||
|
void Destroy();
|
||||||
|
|
||||||
/// Global utility functions used by other objects
|
/// Global utility functions used by other objects
|
||||||
static u32 FindMemoryType(u32 type_filter, vk::MemoryPropertyFlags properties);
|
static u32 FindMemoryType(u32 type_filter, vk::MemoryPropertyFlags properties);
|
||||||
static void CopyBuffer(VKBuffer* src_buffer, VKBuffer* dst_buffer, vk::BufferCopy region);
|
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
|
/// Return a pointer to the mapped memory if the buffer is host mapped
|
||||||
u8* GetHostPointer() { return reinterpret_cast<u8*>(memory); }
|
u8* GetHostPointer() const { return reinterpret_cast<u8*>(memory); }
|
||||||
vk::Buffer& GetBuffer() { return buffer; }
|
const vk::BufferView& GetView(u32 i = 0) const { return views[i]; }
|
||||||
|
vk::Buffer GetBuffer() const { return buffer; }
|
||||||
u32 GetSize() const { return buffer_info.size; }
|
u32 GetSize() const { return buffer_info.size; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -42,6 +50,8 @@ private:
|
|||||||
void* memory = nullptr;
|
void* memory = nullptr;
|
||||||
vk::Buffer buffer;
|
vk::Buffer buffer;
|
||||||
vk::DeviceMemory buffer_memory;
|
vk::DeviceMemory buffer_memory;
|
||||||
|
u32 view_count = 0;
|
||||||
|
std::array<vk::BufferView, MAX_BUFFER_VIEWS> views;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2020 Citra Emulator Project
|
// Copyright 2022 Citra Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
@@ -12,386 +12,12 @@ namespace Vulkan {
|
|||||||
|
|
||||||
using PixelFormat = SurfaceParams::PixelFormat;
|
using PixelFormat = SurfaceParams::PixelFormat;
|
||||||
|
|
||||||
class RGBA4toRGB5A1 final : public FormatReinterpreterBase {
|
FormatReinterpreterVulkan::FormatReinterpreterVulkan() {
|
||||||
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>());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FormatReinterpreterOpenGL::~FormatReinterpreterOpenGL() = default;
|
std::pair<FormatReinterpreterVulkan::ReinterpreterMap::iterator,
|
||||||
|
FormatReinterpreterVulkan::ReinterpreterMap::iterator>
|
||||||
std::pair<FormatReinterpreterOpenGL::ReinterpreterMap::iterator,
|
FormatReinterpreterVulkan::GetPossibleReinterpretations(PixelFormat dst_format) {
|
||||||
FormatReinterpreterOpenGL::ReinterpreterMap::iterator>
|
|
||||||
FormatReinterpreterOpenGL::GetPossibleReinterpretations(PixelFormat dst_format) {
|
|
||||||
return reinterpreters.equal_range(dst_format);
|
return reinterpreters.equal_range(dst_format);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -51,7 +51,7 @@ class FormatReinterpreterVulkan : NonCopyable {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
explicit FormatReinterpreterVulkan();
|
explicit FormatReinterpreterVulkan();
|
||||||
~FormatReinterpreterVulkan();
|
~FormatReinterpreterVulkan() = default;
|
||||||
|
|
||||||
std::pair<ReinterpreterMap::iterator, ReinterpreterMap::iterator> GetPossibleReinterpretations(
|
std::pair<ReinterpreterMap::iterator, ReinterpreterMap::iterator> GetPossibleReinterpretations(
|
||||||
SurfaceParams::PixelFormat dst_format);
|
SurfaceParams::PixelFormat dst_format);
|
||||||
|
@@ -13,8 +13,7 @@
|
|||||||
namespace Vulkan {
|
namespace Vulkan {
|
||||||
|
|
||||||
/// The global Vulkan instance
|
/// The global Vulkan instance
|
||||||
class VKInstance
|
class VKInstance {
|
||||||
{
|
|
||||||
public:
|
public:
|
||||||
VKInstance() = default;
|
VKInstance() = default;
|
||||||
~VKInstance();
|
~VKInstance();
|
||||||
|
@@ -11,27 +11,85 @@
|
|||||||
|
|
||||||
namespace Vulkan {
|
namespace Vulkan {
|
||||||
|
|
||||||
PipelineBuilder::PipelineBuilder() {
|
PipelineLayoutBuilder::PipelineLayoutBuilder() {
|
||||||
vertex_input_state = vk::PipelineVertexInputStateCreateInfo{
|
Clear();
|
||||||
{}, HardwareVertex::binding_desc, HardwareVertex::attribute_desc
|
}
|
||||||
};
|
|
||||||
|
|
||||||
/* Include all required pointers to the pipeline info structure */
|
void PipelineLayoutBuilder::Clear() {
|
||||||
vk::GraphicsPipelineCreateInfo pipeline_info{
|
pipeline_layout_info = vk::PipelineLayoutCreateInfo{};
|
||||||
{}, 0, shader_stages.data(), &vertex_input_state, &input_assembly, nullptr,
|
}
|
||||||
&viewport_state, &rasterization_state, &multisample_info, &depth_state,
|
|
||||||
&blend_state, &dynamic_info, nullptr, nullptr };
|
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() {
|
vk::Pipeline PipelineBuilder::Build() {
|
||||||
auto& device = g_vk_instace->GetDevice();
|
auto& device = g_vk_instace->GetDevice();
|
||||||
auto result = device.createGraphicsPipeline({}, pipeline_info);
|
|
||||||
|
|
||||||
|
auto result = device.createGraphicsPipeline({}, pipeline_info);
|
||||||
if (result.result != vk::Result::eSuccess) {
|
if (result.result != vk::Result::eSuccess) {
|
||||||
LOG_CRITICAL(Render_Vulkan, "Failed to build vulkan pipeline!");
|
LOG_CRITICAL(Render_Vulkan, "Failed to build vulkan pipeline!");
|
||||||
UNREACHABLE();
|
UNREACHABLE();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Clear();
|
||||||
return result.value;
|
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) {
|
void PipelineBuilder::SetPrimitiveTopology(vk::PrimitiveTopology topology, bool enable_primitive_restart) {
|
||||||
input_assembly.topology = topology;
|
input_assembly.topology = topology;
|
||||||
input_assembly.primitiveRestartEnable = enable_primitive_restart;
|
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.polygonMode = polygon_mode;
|
||||||
rasterization_state.cullMode = cull_mode;
|
rasterization_state.cullMode = cull_mode;
|
||||||
rasterization_state.frontFace = front_face;
|
rasterization_state.frontFace = front_face;
|
||||||
|
pipeline_info.pRasterizationState = &rasterization_state;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PipelineBuilder::SetLineWidth(float width) {
|
void PipelineBuilder::SetLineWidth(float width) {
|
||||||
rasterization_state.lineWidth = width;
|
rasterization_state.lineWidth = width;
|
||||||
|
pipeline_info.pRasterizationState = &rasterization_state;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PipelineBuilder::SetMultisamples(u32 multisamples, bool per_sample_shading) {
|
void PipelineBuilder::SetMultisamples(vk::SampleCountFlagBits samples, bool per_sample_shading) {
|
||||||
multisample_info.rasterizationSamples = static_cast<vk::SampleCountFlagBits>(multisamples);
|
multisample_info.rasterizationSamples = samples;
|
||||||
multisample_info.sampleShadingEnable = per_sample_shading;
|
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) {
|
void PipelineBuilder::SetDepthState(bool depth_test, bool depth_write, vk::CompareOp compare_op) {
|
||||||
depth_state.depthTestEnable = depth_test;
|
depth_state.depthTestEnable = depth_test;
|
||||||
depth_state.depthWriteEnable = depth_write;
|
depth_state.depthWriteEnable = depth_write;
|
||||||
depth_state.depthCompareOp = compare_op;
|
depth_state.depthCompareOp = compare_op;
|
||||||
|
pipeline_info.pDepthStencilState = &depth_state;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PipelineBuilder::SetStencilState(bool stencil_test, vk::StencilOpState front, vk::StencilOpState back) {
|
void PipelineBuilder::SetStencilState(bool stencil_test, vk::StencilOpState front, vk::StencilOpState back) {
|
||||||
depth_state.stencilTestEnable = stencil_test;
|
depth_state.stencilTestEnable = stencil_test;
|
||||||
depth_state.front = front;
|
depth_state.front = front;
|
||||||
depth_state.back = back;
|
depth_state.back = back;
|
||||||
|
pipeline_info.pDepthStencilState = &depth_state;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PipelineBuilder::SetBlendConstants(float r, float g, float b, float a) {
|
void PipelineBuilder::SetBlendConstants(float r, float g, float b, float a) {
|
||||||
blend_state.blendConstants = std::array<float, 4>{r, g, b, 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,
|
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.attachmentCount = 1;
|
||||||
blend_state.pAttachments = &blend_attachment;
|
blend_state.pAttachments = &blend_attachment;
|
||||||
|
pipeline_info.pColorBlendState = &blend_state;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PipelineBuilder::SetNoBlendingState() {
|
void PipelineBuilder::SetNoBlendingState() {
|
||||||
@@ -116,29 +202,33 @@ void PipelineBuilder::SetNoBlendingState() {
|
|||||||
vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA);
|
vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PipelineBuilder::AddDynamicState(vk::DynamicState state) {
|
void PipelineBuilder::SetDynamicStates(std::span<vk::DynamicState> states) {
|
||||||
if (dynamic_info.dynamicStateCount < MAX_DYNAMIC_STATES) {
|
if (states.size() > MAX_DYNAMIC_STATES) {
|
||||||
dynamic_states[dynamic_info.dynamicStateCount] = state;
|
LOG_ERROR(Render_Vulkan, "Cannot include more dynamic states!");
|
||||||
|
UNREACHABLE();
|
||||||
dynamic_info.dynamicStateCount++;
|
|
||||||
dynamic_info.pDynamicStates = dynamic_states.data();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_ERROR(Render_Vulkan, "Cannot include more dynamic states!");
|
// Copy the state data
|
||||||
UNREACHABLE();
|
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) {
|
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 = vk::Viewport{ x, y, width, height, min_depth, max_depth };
|
||||||
viewport_state.pViewports = &viewport;
|
viewport_state.pViewports = &viewport;
|
||||||
viewport_state.viewportCount = 1;
|
viewport_state.viewportCount = 1;
|
||||||
|
pipeline_info.pViewportState = &viewport_state;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PipelineBuilder::SetScissorRect(s32 x, s32 y, u32 width, u32 height) {
|
void PipelineBuilder::SetScissorRect(s32 x, s32 y, u32 width, u32 height) {
|
||||||
scissor = vk::Rect2D{{x, y}, {width, height}};
|
scissor = vk::Rect2D{{x, y}, {width, height}};
|
||||||
viewport_state.pScissors = &scissor;
|
|
||||||
viewport_state.scissorCount = 1u;
|
viewport_state.scissorCount = 1u;
|
||||||
|
viewport_state.pScissors = &scissor;
|
||||||
|
pipeline_info.pViewportState = &viewport_state;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Vulkan
|
} // namespace Vulkan
|
||||||
|
@@ -15,25 +15,46 @@
|
|||||||
|
|
||||||
namespace Vulkan {
|
namespace Vulkan {
|
||||||
|
|
||||||
constexpr u32 MAX_DYNAMIC_STATES = 14;
|
class PipelineLayoutBuilder {
|
||||||
constexpr u32 MAX_SHADER_STAGES = 3;
|
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 {
|
class PipelineBuilder {
|
||||||
public:
|
public:
|
||||||
PipelineBuilder();
|
PipelineBuilder();
|
||||||
~PipelineBuilder() = default;
|
~PipelineBuilder() = default;
|
||||||
|
|
||||||
|
void Clear();
|
||||||
vk::Pipeline Build();
|
vk::Pipeline Build();
|
||||||
|
|
||||||
void SetPipelineLayout(vk::PipelineLayout layout);
|
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 SetShaderStage(vk::ShaderStageFlagBits stage, vk::ShaderModule module);
|
||||||
|
|
||||||
void SetPrimitiveTopology(vk::PrimitiveTopology topology, bool enable_primitive_restart = false);
|
void SetPrimitiveTopology(vk::PrimitiveTopology topology, bool enable_primitive_restart = false);
|
||||||
void SetLineWidth(float width);
|
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,
|
void SetRasterizationState(vk::PolygonMode polygon_mode, vk::CullModeFlags cull_mode,
|
||||||
vk::FrontFace front_face);
|
vk::FrontFace front_face);
|
||||||
|
|
||||||
|
void SetNoCullRasterizationState();
|
||||||
void SetDepthState(bool depth_test, bool depth_write, vk::CompareOp compare_op);
|
void SetDepthState(bool depth_test, bool depth_write, vk::CompareOp compare_op);
|
||||||
void SetStencilState(bool stencil_test, vk::StencilOpState front, vk::StencilOpState back);
|
void SetStencilState(bool stencil_test, vk::StencilOpState front, vk::StencilOpState back);
|
||||||
void SetNoDepthTestState();
|
void SetNoDepthTestState();
|
||||||
@@ -47,14 +68,21 @@ public:
|
|||||||
|
|
||||||
void SetViewport(float x, float y, float width, float height, float min_depth, float max_depth);
|
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 SetScissorRect(s32 x, s32 y, u32 width, u32 height);
|
||||||
void AddDynamicState(vk::DynamicState state);
|
void SetDynamicStates(std::span<vk::DynamicState> states);
|
||||||
void SetMultisamples(vk::SampleCountFlagBits samples);
|
|
||||||
|
|
||||||
private:
|
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;
|
vk::GraphicsPipelineCreateInfo pipeline_info;
|
||||||
std::vector<vk::PipelineShaderStageCreateInfo> shader_stages;
|
std::vector<vk::PipelineShaderStageCreateInfo> shader_stages;
|
||||||
|
|
||||||
vk::PipelineVertexInputStateCreateInfo vertex_input_state;
|
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::PipelineInputAssemblyStateCreateInfo input_assembly;
|
||||||
vk::PipelineRasterizationStateCreateInfo rasterization_state;
|
vk::PipelineRasterizationStateCreateInfo rasterization_state;
|
||||||
vk::PipelineDepthStencilStateCreateInfo depth_state;
|
vk::PipelineDepthStencilStateCreateInfo depth_state;
|
||||||
|
@@ -76,7 +76,10 @@ RasterizerVulkan::RasterizerVulkan(Frontend::EmuWindow& emu_window) {
|
|||||||
.usage = vk::BufferUsageFlagBits::eStorageTexelBuffer,
|
.usage = vk::BufferUsageFlagBits::eStorageTexelBuffer,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
texel_buffer_info.view_formats[0] = vk::Format::eR32G32Sfloat;
|
||||||
texture_buffer_lut_lf.Create(texel_buffer_info);
|
texture_buffer_lut_lf.Create(texel_buffer_info);
|
||||||
|
|
||||||
|
texel_buffer_info.view_formats[1] = vk::Format::eR32G32B32A32Sfloat;
|
||||||
texture_buffer_lut.Create(texel_buffer_info);
|
texture_buffer_lut.Create(texel_buffer_info);
|
||||||
|
|
||||||
// Create and bind uniform buffers
|
// Create and bind uniform buffers
|
||||||
@@ -87,13 +90,15 @@ RasterizerVulkan::RasterizerVulkan(Frontend::EmuWindow& emu_window) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
uniform_buffer.Create(uniform_info);
|
uniform_buffer.Create(uniform_info);
|
||||||
state.SetUniformBuffer(BindingID::VertexUniform, &uniform_buffer, 0, uniform_size_aligned_vs);
|
state.SetUniformBuffer(BindingID::VertexUniform, 0, uniform_size_aligned_vs,
|
||||||
state.SetUniformBuffer(BindingID::PicaUniform, &uniform_buffer, uniform_size_aligned_vs, uniform_size_aligned_fs);
|
uniform_buffer);
|
||||||
|
state.SetUniformBuffer(BindingID::PicaUniform, uniform_size_aligned_vs, uniform_size_aligned_fs,
|
||||||
|
uniform_buffer);
|
||||||
|
|
||||||
// Bind texel buffers
|
// Bind texel buffers
|
||||||
state.SetTexelBuffer(BindingID::LutLF, &texture_buffer_lut_lf, vk::Format::eR32G32Sfloat);
|
state.SetTexelBuffer(BindingID::LutLF, 0, TEXTURE_BUFFER_SIZE, texture_buffer_lut_lf, 0);
|
||||||
state.SetTexelBuffer(BindingID::LutRG, &texture_buffer_lut, vk::Format::eR32G32Sfloat);
|
state.SetTexelBuffer(BindingID::LutRG, 0, TEXTURE_BUFFER_SIZE, texture_buffer_lut, 0);
|
||||||
state.SetTexelBuffer(BindingID::LutRGBA, &texture_buffer_lut, vk::Format::eR32G32B32A32Sfloat);
|
state.SetTexelBuffer(BindingID::LutRGBA, 0, TEXTURE_BUFFER_SIZE, texture_buffer_lut, 1);
|
||||||
|
|
||||||
// Create vertex and index buffers
|
// Create vertex and index buffers
|
||||||
VKBuffer::Info vertex_info = {
|
VKBuffer::Info vertex_info = {
|
||||||
@@ -331,7 +336,7 @@ bool RasterizerVulkan::Draw(bool accelerate, bool is_indexed) {
|
|||||||
//texture_samplers[texture_index].SyncWithConfig(texture.config);
|
//texture_samplers[texture_index].SyncWithConfig(texture.config);
|
||||||
Surface surface = res_cache.GetTextureSurface(texture);
|
Surface surface = res_cache.GetTextureSurface(texture);
|
||||||
if (surface != nullptr) {
|
if (surface != nullptr) {
|
||||||
state.SetTexture(BindingID::Tex0 + texture_index, &surface->texture);
|
state.SetTexture(BindingID::Tex0 + texture_index, surface->texture);
|
||||||
} else {
|
} else {
|
||||||
// Can occur when texture addr is null or its memory is unmapped/invalid
|
// 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
|
// 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();
|
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
|
// Mark framebuffer surfaces as dirty
|
||||||
Common::Rectangle<u32> draw_rect_unscaled{draw_rect.left / res_scale, draw_rect.top / res_scale,
|
Common::Rectangle<u32> draw_rect_unscaled{draw_rect.left / res_scale, draw_rect.top / res_scale,
|
||||||
draw_rect.right / 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.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);
|
(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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -280,15 +280,12 @@ private:
|
|||||||
static constexpr std::size_t UNIFORM_BUFFER_SIZE = 2 * 1024 * 1024;
|
static constexpr std::size_t UNIFORM_BUFFER_SIZE = 2 * 1024 * 1024;
|
||||||
static constexpr std::size_t TEXTURE_BUFFER_SIZE = 1 * 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 vertex_buffer, uniform_buffer, index_buffer;
|
||||||
VKBuffer texture_buffer_lut_lf, texture_buffer_lut;
|
VKBuffer texture_buffer_lut_lf, texture_buffer_lut;
|
||||||
|
|
||||||
u32 uniform_buffer_alignment;
|
u32 uniform_buffer_alignment;
|
||||||
u32 uniform_size_aligned_vs, uniform_size_aligned_fs;
|
u32 uniform_size_aligned_vs, uniform_size_aligned_fs;
|
||||||
|
|
||||||
SamplerInfo texture_cube_sampler;
|
|
||||||
|
|
||||||
std::array<std::array<glm::vec2, 256>,
|
std::array<std::array<glm::vec2, 256>,
|
||||||
Pica::LightingRegs::NumLightingSampler> lighting_lut_data{};
|
Pica::LightingRegs::NumLightingSampler> lighting_lut_data{};
|
||||||
std::array<glm::vec2, 128> fog_lut_data{};
|
std::array<glm::vec2, 128> fog_lut_data{};
|
||||||
|
@@ -36,6 +36,51 @@ bool operator <(BindingID lhs, BindingID rhs) {
|
|||||||
static_cast<u32>(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() {
|
VulkanState::VulkanState() {
|
||||||
// Create a dummy texture which can be used in place of a real binding.
|
// Create a dummy texture which can be used in place of a real binding.
|
||||||
VKTexture::Info info = {
|
VKTexture::Info info = {
|
||||||
@@ -46,20 +91,8 @@ VulkanState::VulkanState() {
|
|||||||
.view_type = vk::ImageViewType::e2D
|
.view_type = vk::ImageViewType::e2D
|
||||||
};
|
};
|
||||||
|
|
||||||
dummy_texture.Create(info);
|
placeholder.Create(info);
|
||||||
dummy_texture.Transition(vk::ImageLayout::eShaderReadOnlyOptimal);
|
placeholder.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);
|
|
||||||
|
|
||||||
// Create texture sampler
|
// Create texture sampler
|
||||||
auto props = g_vk_instace->GetPhysicalDevice().getProperties();
|
auto props = g_vk_instace->GetPhysicalDevice().getProperties();
|
||||||
@@ -71,6 +104,8 @@ VulkanState::VulkanState() {
|
|||||||
false, vk::CompareOp::eAlways, {}, {},
|
false, vk::CompareOp::eAlways, {}, {},
|
||||||
vk::BorderColor::eIntOpaqueBlack, false
|
vk::BorderColor::eIntOpaqueBlack, false
|
||||||
};
|
};
|
||||||
|
|
||||||
|
auto& device = g_vk_instace->GetDevice();
|
||||||
sampler = device.createSamplerUnique(sampler_info);
|
sampler = device.createSamplerUnique(sampler_info);
|
||||||
|
|
||||||
// Compile trivial vertex shader
|
// Compile trivial vertex shader
|
||||||
@@ -105,60 +140,44 @@ void VulkanState::SetVertexBuffer(VKBuffer* buffer, vk::DeviceSize offset) {
|
|||||||
dirty_flags |= DirtyFlags::VertexBuffer;
|
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);
|
assert(id < BindingID::Tex0);
|
||||||
u32 index = static_cast<u32>(id);
|
u32 binding = static_cast<u32>(id);
|
||||||
|
|
||||||
auto& binding = bindings[index];
|
updater.SetDescriptorSet(g_vk_task_scheduler->GetDescriptorSet(0));
|
||||||
auto old_buffer = std::get<VKBuffer*>(binding.resource);
|
updater.AddBufferDescriptorWrite(binding, vk::DescriptorType::eUniformBuffer, offset, size, buffer);
|
||||||
if (old_buffer != buffer) {
|
|
||||||
binding.resource = buffer;
|
|
||||||
dirty_flags |= DirtyFlags::Uniform;
|
|
||||||
binding.dirty = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanState::SetTexture(BindingID id, VKTexture* image) {
|
void VulkanState::SetTexture(BindingID id, const VKTexture& image) {
|
||||||
assert(id > BindingID::PicaUniform && id < BindingID::LutLF);
|
assert(id > BindingID::PicaUniform && id < BindingID::LutLF);
|
||||||
u32 index = static_cast<u32>(id);
|
u32 binding = static_cast<u32>(id);
|
||||||
|
|
||||||
auto& binding = bindings[index];
|
updater.SetDescriptorSet(g_vk_task_scheduler->GetDescriptorSet(1));
|
||||||
auto old_image = std::get<VKTexture*>(binding.resource);
|
updater.AddCombinedImageSamplerDescriptorWrite(binding, sampler.get(), image);
|
||||||
if (old_image != image) {
|
|
||||||
binding.resource = image;
|
|
||||||
dirty_flags |= DirtyFlags::Texture;
|
|
||||||
binding.dirty = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
assert(id > BindingID::TexCube);
|
||||||
u32 index = static_cast<u32>(id);
|
u32 binding = static_cast<u32>(id);
|
||||||
|
|
||||||
auto& binding = bindings[index];
|
updater.SetDescriptorSet(g_vk_task_scheduler->GetDescriptorSet(2));
|
||||||
auto old_buffer = std::get<VKBuffer*>(binding.resource);
|
updater.AddBufferDescriptorWrite(binding, vk::DescriptorType::eStorageTexelBuffer,
|
||||||
if (old_buffer != buffer) {
|
offset, size, buffer, buffer.GetView(view_index));
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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++) {
|
for (auto i = u32(BindingID::Tex0); i <= u32(BindingID::TexCube); i++) {
|
||||||
auto current_image = std::get<VKTexture*>(bindings[i].resource);
|
auto view = updater.GetResource<vk::ImageView>(i);
|
||||||
if (current_image == image) {
|
if (view == image.GetView()) {
|
||||||
UnbindTexture(i);
|
updater.AddCombinedImageSamplerDescriptorWrite(i, sampler.get(), placeholder);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanState::UnbindTexture(u32 index) {
|
void VulkanState::UnbindTexture(u32 index) {
|
||||||
bindings[index].resource = &dummy_texture;
|
updater.SetDescriptorSet(g_vk_task_scheduler->GetDescriptorSet(1));
|
||||||
dirty_flags |= DirtyFlags::Texture;
|
updater.AddCombinedImageSamplerDescriptorWrite(index, sampler.get(), placeholder);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanState::BeginRendering(Attachment color, Attachment depth_stencil) {
|
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() {
|
void VulkanState::Apply() {
|
||||||
// Update resources in descriptor sets if changed
|
// Update resources in descriptor sets if changed
|
||||||
UpdateDescriptorSet();
|
updater.Update();
|
||||||
|
|
||||||
// Re-apply dynamic parts of the pipeline
|
// Re-apply dynamic parts of the pipeline
|
||||||
auto command_buffer = g_vk_task_scheduler->GetCommandBuffer();
|
auto command_buffer = g_vk_task_scheduler->GetCommandBuffer();
|
||||||
@@ -444,21 +463,21 @@ void VulkanState::Apply() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void VulkanState::ConfigureDescriptorSets() {
|
void VulkanState::ConfigureDescriptorSets() {
|
||||||
// Define the descriptor sets we will be using
|
// Draw descriptor sets
|
||||||
std::array<vk::DescriptorSetLayoutBinding, 2> ubo_set = {{
|
std::array<vk::DescriptorSetLayoutBinding, 2> ubo_set{{
|
||||||
{ 0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex |
|
{ 0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex |
|
||||||
vk::ShaderStageFlagBits::eGeometry | vk::ShaderStageFlagBits::eFragment }, // shader_data
|
vk::ShaderStageFlagBits::eGeometry | vk::ShaderStageFlagBits::eFragment }, // shader_data
|
||||||
{ 1, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex } // pica_uniforms
|
{ 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
|
{ 0, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment }, // tex0
|
||||||
{ 1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment }, // tex1
|
{ 1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment }, // tex1
|
||||||
{ 2, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment }, // tex2
|
{ 2, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment }, // tex2
|
||||||
{ 3, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment }, // tex_cube
|
{ 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
|
{ 0, vk::DescriptorType::eStorageTexelBuffer, 1, vk::ShaderStageFlagBits::eFragment }, // texture_buffer_lut_lf
|
||||||
{ 1, vk::DescriptorType::eStorageTexelBuffer, 1, vk::ShaderStageFlagBits::eFragment }, // texture_buffer_lut_rg
|
{ 1, vk::DescriptorType::eStorageTexelBuffer, 1, vk::ShaderStageFlagBits::eFragment }, // texture_buffer_lut_rg
|
||||||
{ 2, vk::DescriptorType::eStorageTexelBuffer, 1, vk::ShaderStageFlagBits::eFragment } // texture_buffer_lut_rgba
|
{ 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]);
|
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
|
// Create the standard descriptor set layout
|
||||||
vk::PipelineLayoutCreateInfo layout_info({}, descriptor_layouts);
|
vk::PipelineLayoutCreateInfo layout_info({}, descriptor_layouts);
|
||||||
pipeline_layout = device.createPipelineLayoutUnique(layout_info);
|
pipeline_layout = device.createPipelineLayoutUnique(layout_info);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanState::ConfigurePipeline() {
|
void VulkanState::ConfigurePipeline() {
|
||||||
@@ -505,7 +520,7 @@ void VulkanState::ConfigurePipeline() {
|
|||||||
|
|
||||||
|
|
||||||
// Enable every required dynamic state
|
// 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::eDepthCompareOp, vk::DynamicState::eLineWidth,
|
||||||
vk::DynamicState::eDepthTestEnable, vk::DynamicState::eColorWriteEnableEXT,
|
vk::DynamicState::eDepthTestEnable, vk::DynamicState::eColorWriteEnableEXT,
|
||||||
vk::DynamicState::eStencilTestEnable, vk::DynamicState::eStencilOp,
|
vk::DynamicState::eStencilTestEnable, vk::DynamicState::eStencilOp,
|
||||||
@@ -515,61 +530,14 @@ void VulkanState::ConfigurePipeline() {
|
|||||||
vk::DynamicState::eLogicOpEXT, vk::DynamicState::eFrontFace
|
vk::DynamicState::eLogicOpEXT, vk::DynamicState::eFrontFace
|
||||||
};
|
};
|
||||||
|
|
||||||
for (auto& state : dynamic_states) {
|
builder.SetDynamicStates(dynamic_states);
|
||||||
builder.AddDynamicState(state);
|
|
||||||
}
|
// Configure vertex buffer
|
||||||
|
auto attributes = HardwareVertex::attribute_desc;
|
||||||
|
builder.AddVertexBuffer(0, sizeof(HardwareVertex), vk::VertexInputRate::eVertex, attributes);
|
||||||
|
|
||||||
// Add trivial vertex shader
|
// Add trivial vertex shader
|
||||||
builder.SetShaderStage(vk::ShaderStageFlagBits::eVertex, trivial_vertex_shader.get());
|
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
|
} // namespace Vulkan
|
||||||
|
@@ -64,6 +64,41 @@ struct Attachment {
|
|||||||
|
|
||||||
constexpr u32 DESCRIPTOR_SET_LAYOUT_COUNT = 3;
|
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
|
/// Tracks global Vulkan state
|
||||||
class VulkanState {
|
class VulkanState {
|
||||||
public:
|
public:
|
||||||
@@ -73,6 +108,7 @@ public:
|
|||||||
/// Initialize object to its initial state
|
/// Initialize object to its initial state
|
||||||
static void Create();
|
static void Create();
|
||||||
static VulkanState& Get();
|
static VulkanState& Get();
|
||||||
|
static vk::ShaderModule CompileShader(const std::string& source, vk::ShaderStageFlagBits stage);
|
||||||
|
|
||||||
/// Query state
|
/// Query state
|
||||||
bool DepthTestEnabled() const { return depth_enabled && depth_writes; }
|
bool DepthTestEnabled() const { return depth_enabled && depth_writes; }
|
||||||
@@ -88,7 +124,7 @@ public:
|
|||||||
void SetStencilWrite(u32 mask);
|
void SetStencilWrite(u32 mask);
|
||||||
void SetStencilInput(u32 mask);
|
void SetStencilInput(u32 mask);
|
||||||
void SetStencilTest(bool enable, vk::StencilOp fail, vk::StencilOp pass, vk::StencilOp depth_fail,
|
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 SetDepthWrite(bool enable);
|
||||||
void SetDepthTest(bool enable, vk::CompareOp compare);
|
void SetDepthTest(bool enable, vk::CompareOp compare);
|
||||||
void SetColorMask(bool red, bool green, bool blue, bool alpha);
|
void SetColorMask(bool red, bool green, bool blue, bool alpha);
|
||||||
@@ -103,10 +139,10 @@ public:
|
|||||||
void EndRendering();
|
void EndRendering();
|
||||||
|
|
||||||
/// Configure shader resources
|
/// Configure shader resources
|
||||||
void SetUniformBuffer(BindingID id, VKBuffer* buffer, u32 offset, u32 size);
|
void SetUniformBuffer(BindingID id, u32 offset, u32 size, const VKBuffer& buffer);
|
||||||
void SetTexture(BindingID id, VKTexture* texture);
|
void SetTexture(BindingID id, const VKTexture& texture);
|
||||||
void SetTexelBuffer(BindingID id, VKBuffer* buffer, vk::Format view_format);
|
void SetTexelBuffer(BindingID id, u32 offset, u32 size, const VKBuffer& buffer, u32 view_index);
|
||||||
void UnbindTexture(VKTexture* image);
|
void UnbindTexture(const VKTexture& image);
|
||||||
void UnbindTexture(u32 index);
|
void UnbindTexture(u32 index);
|
||||||
|
|
||||||
/// Apply all dirty state to the current Vulkan command buffer
|
/// Apply all dirty state to the current Vulkan command buffer
|
||||||
@@ -115,27 +151,18 @@ public:
|
|||||||
private:
|
private:
|
||||||
void ConfigureDescriptorSets();
|
void ConfigureDescriptorSets();
|
||||||
void ConfigurePipeline();
|
void ConfigurePipeline();
|
||||||
void UpdateDescriptorSet();
|
|
||||||
vk::ShaderModule CompileShader(const std::string& source, vk::ShaderStageFlagBits stage);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct Binding {
|
|
||||||
bool dirty{};
|
|
||||||
std::variant<VKBuffer*, VKTexture*> resource{};
|
|
||||||
vk::UniqueBufferView buffer_view{};
|
|
||||||
};
|
|
||||||
|
|
||||||
DirtyFlags dirty_flags;
|
DirtyFlags dirty_flags;
|
||||||
bool rendering = false;
|
DescriptorUpdater updater;
|
||||||
VKTexture dummy_texture;
|
VKTexture placeholder;
|
||||||
vk::UniqueSampler sampler;
|
vk::UniqueSampler sampler;
|
||||||
|
|
||||||
|
// Vertex buffer
|
||||||
VKBuffer* vertex_buffer{}, * index_buffer{};
|
VKBuffer* vertex_buffer{}, * index_buffer{};
|
||||||
vk::DeviceSize vertex_offset, index_offset;
|
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::Viewport viewport{0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f};
|
||||||
vk::CullModeFlags cull_mode{};
|
vk::CullModeFlags cull_mode{};
|
||||||
vk::FrontFace front_face{};
|
vk::FrontFace front_face{};
|
||||||
@@ -143,6 +170,7 @@ private:
|
|||||||
vk::LogicOp logic_op{};
|
vk::LogicOp logic_op{};
|
||||||
std::array<float, 4> blend_constants{};
|
std::array<float, 4> blend_constants{};
|
||||||
|
|
||||||
|
bool rendering = false;
|
||||||
u32 stencil_write_mask{}, stencil_input_mask{}, stencil_ref{};
|
u32 stencil_write_mask{}, stencil_input_mask{}, stencil_ref{};
|
||||||
bool depth_enabled{}, depth_writes{}, stencil_enabled{}, stencil_writes{};
|
bool depth_enabled{}, depth_writes{}, stencil_enabled{}, stencil_writes{};
|
||||||
vk::StencilOp fail_op, pass_op, depth_fail_op;
|
vk::StencilOp fail_op, pass_op, depth_fail_op;
|
||||||
@@ -159,4 +187,20 @@ private:
|
|||||||
std::unordered_map<PipelineCacheKey, vk::UniquePipeline> pipelines;
|
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
|
} // namespace Vulkan
|
||||||
|
@@ -189,17 +189,11 @@ void VKSwapChain::SetupImages() {
|
|||||||
|
|
||||||
// Create the swapchain buffers containing the image and imageview
|
// Create the swapchain buffers containing the image and imageview
|
||||||
swapchain_images.resize(images.size());
|
swapchain_images.resize(images.size());
|
||||||
for (int i = 0; i < swapchain_images.size(); i++)
|
for (int i = 0; i < swapchain_images.size(); i++) {
|
||||||
{
|
vk::ImageViewCreateInfo color_attachment_view{
|
||||||
vk::ImageViewCreateInfo color_attachment_view
|
{}, images[i], vk::ImageViewType::e2D, details.format.format, {},
|
||||||
(
|
|
||||||
{},
|
|
||||||
images[i],
|
|
||||||
vk::ImageViewType::e2D,
|
|
||||||
details.format.format,
|
|
||||||
{},
|
|
||||||
{ vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 }
|
{ vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 }
|
||||||
);
|
};
|
||||||
|
|
||||||
// Wrap swapchain images with VKTexture
|
// Wrap swapchain images with VKTexture
|
||||||
swapchain_images[i].image = images[i];
|
swapchain_images[i].image = images[i];
|
||||||
|
@@ -9,9 +9,6 @@
|
|||||||
|
|
||||||
namespace Vulkan {
|
namespace Vulkan {
|
||||||
|
|
||||||
VKTaskScheduler::VKTaskScheduler(VKSwapChain* swapchain) :
|
|
||||||
swapchain(swapchain) {}
|
|
||||||
|
|
||||||
VKTaskScheduler::~VKTaskScheduler() {
|
VKTaskScheduler::~VKTaskScheduler() {
|
||||||
SyncToGPU();
|
SyncToGPU();
|
||||||
}
|
}
|
||||||
@@ -50,23 +47,26 @@ bool VKTaskScheduler::Create() {
|
|||||||
.usage = vk::BufferUsageFlagBits::eTransferSrc
|
.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) {
|
for (auto& task : tasks) {
|
||||||
// Create command buffers
|
// Create command buffers
|
||||||
vk::CommandBufferAllocateInfo buffer_info
|
vk::CommandBufferAllocateInfo buffer_info{
|
||||||
(
|
command_pool.get(), vk::CommandBufferLevel::ePrimary, 1, task.command_buffer
|
||||||
command_pool.get(),
|
};
|
||||||
vk::CommandBufferLevel::ePrimary,
|
|
||||||
1, task.command_buffer
|
|
||||||
);
|
|
||||||
|
|
||||||
task.command_buffer = device.allocateCommandBuffers(buffer_info)[0];
|
task.command_buffer = device.allocateCommandBuffers(buffer_info)[0];
|
||||||
|
|
||||||
// Create staging buffer
|
// Create staging buffer
|
||||||
task.staging.Create(staging_info);
|
task.staging.Create(staging_info);
|
||||||
}
|
|
||||||
|
|
||||||
// Create present semaphore
|
// Create descriptor pool
|
||||||
present_semaphore = device.createSemaphoreUnique({});
|
vk::DescriptorPoolCreateInfo pool_create_info({}, 1024, pool_sizes);
|
||||||
|
task.desc_pool = device.createDescriptorPoolUnique(pool_create_info);
|
||||||
|
}
|
||||||
|
|
||||||
// Activate the first task.
|
// Activate the first task.
|
||||||
BeginTask();
|
BeginTask();
|
||||||
@@ -74,6 +74,14 @@ bool VKTaskScheduler::Create() {
|
|||||||
return true;
|
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) {
|
void VKTaskScheduler::SyncToGPU(u64 task_index) {
|
||||||
// No need to sync if the GPU already has finished the task
|
// No need to sync if the GPU already has finished the task
|
||||||
if (tasks[task_index].task_id <= GetGPUTick()) {
|
if (tasks[task_index].task_id <= GetGPUTick()) {
|
||||||
@@ -106,7 +114,7 @@ void VKTaskScheduler::SyncToGPU() {
|
|||||||
SyncToGPU(current_task);
|
SyncToGPU(current_task);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VKTaskScheduler::Submit(bool present, bool wait_completion) {
|
void VKTaskScheduler::Submit(bool wait_completion) {
|
||||||
// End the current task recording.
|
// End the current task recording.
|
||||||
auto& task = tasks[current_task];
|
auto& task = tasks[current_task];
|
||||||
task.command_buffer.end();
|
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
|
// When the task completes the timeline will increment to the task id
|
||||||
vk::TimelineSemaphoreSubmitInfo timeline_info({}, task.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::PipelineStageFlags wait_stage = vk::PipelineStageFlagBits::eColorAttachmentOutput;
|
||||||
vk::SubmitInfo submit_info({}, wait_stage, task.command_buffer, signal_semaphores, &timeline_info);
|
vk::SubmitInfo submit_info({}, wait_stage, task.command_buffer, timeline.get(), &timeline_info);
|
||||||
|
|
||||||
// Wait for new swapchain image
|
|
||||||
if (present) {
|
|
||||||
auto available = swapchain->AcquireNextImage();
|
|
||||||
submit_info.setWaitSemaphores(available);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Submit the command buffer
|
// Submit the command buffer
|
||||||
g_vk_instace->GetGraphicsQueue().submit(submit_info);
|
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
|
// Block host until the GPU catches up
|
||||||
if (wait_completion) {
|
if (wait_completion) {
|
||||||
SyncToGPU();
|
SyncToGPU();
|
||||||
|
@@ -33,16 +33,18 @@ class VKSwapChain;
|
|||||||
/// frame latency if the CPU is too far ahead of the GPU
|
/// frame latency if the CPU is too far ahead of the GPU
|
||||||
class VKTaskScheduler {
|
class VKTaskScheduler {
|
||||||
public:
|
public:
|
||||||
explicit VKTaskScheduler(VKSwapChain* swapchain);
|
VKTaskScheduler() = default;
|
||||||
~VKTaskScheduler();
|
~VKTaskScheduler();
|
||||||
|
|
||||||
/// Create and initialize the work scheduler
|
/// Create and initialize the work scheduler
|
||||||
bool Create();
|
bool Create();
|
||||||
|
|
||||||
/// Retrieve either of the current frame's command buffers
|
/// Retrieve either of the current frame's command buffers
|
||||||
vk::CommandBuffer GetCommandBuffer() const { return tasks[current_task].command_buffer; }
|
vk::CommandBuffer GetCommandBuffer() const;
|
||||||
VKBuffer& GetStaging() { return tasks[current_task].staging; }
|
vk::DescriptorSet GetDescriptorSet(u32 index) const;
|
||||||
|
|
||||||
std::tuple<u8*, u32> RequestStaging(u32 size);
|
std::tuple<u8*, u32> RequestStaging(u32 size);
|
||||||
|
VKBuffer& GetStaging() { return tasks[current_task].staging; }
|
||||||
|
|
||||||
/// Returns the task id that the CPU is recording
|
/// Returns the task id that the CPU is recording
|
||||||
u64 GetCPUTick() const { return current_task_id; }
|
u64 GetCPUTick() const { return current_task_id; }
|
||||||
@@ -58,18 +60,19 @@ public:
|
|||||||
void Schedule(std::function<void()> func);
|
void Schedule(std::function<void()> func);
|
||||||
|
|
||||||
/// Submit the current work batch and move to the next frame
|
/// 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:
|
private:
|
||||||
void BeginTask();
|
void BeginTask();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct Task {
|
struct Task {
|
||||||
u64 task_id{};
|
|
||||||
std::vector<std::function<void()>> cleanups;
|
|
||||||
vk::CommandBuffer command_buffer;
|
|
||||||
VKBuffer staging;
|
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;
|
vk::UniqueSemaphore timeline;
|
||||||
@@ -78,11 +81,7 @@ private:
|
|||||||
|
|
||||||
// Each task contains unique resources
|
// Each task contains unique resources
|
||||||
std::array<Task, CONCURRENT_TASK_COUNT> tasks;
|
std::array<Task, CONCURRENT_TASK_COUNT> tasks;
|
||||||
u32 current_task = CONCURRENT_TASK_COUNT - 1;
|
u64 current_task = 0;
|
||||||
|
|
||||||
// Presentation semaphore
|
|
||||||
vk::UniqueSemaphore present_semaphore;
|
|
||||||
VKSwapChain* swapchain = nullptr;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
extern std::unique_ptr<VKTaskScheduler> g_vk_task_scheduler;
|
extern std::unique_ptr<VKTaskScheduler> g_vk_task_scheduler;
|
||||||
|
@@ -10,49 +10,31 @@
|
|||||||
|
|
||||||
namespace Vulkan {
|
namespace Vulkan {
|
||||||
|
|
||||||
VKTexture::~VKTexture() {
|
static int BytesPerPixel(vk::Format format) {
|
||||||
if (texture) {
|
switch (format) {
|
||||||
// Make sure to unbind the texture before destroying it
|
case vk::Format::eR8G8B8A8Uint:
|
||||||
auto& state = VulkanState::Get();
|
return 4;
|
||||||
state.UnbindTexture(this);
|
case vk::Format::eR8G8B8Uint:
|
||||||
|
return 3;
|
||||||
auto deleter = [this]() {
|
case vk::Format::eR5G6B5UnormPack16:
|
||||||
auto& device = g_vk_instace->GetDevice();
|
case vk::Format::eR5G5B5A1UnormPack16:
|
||||||
device.destroyImage(texture);
|
case vk::Format::eR4G4B4A4UnormPack16:
|
||||||
device.destroyImageView(view);
|
return 2;
|
||||||
device.freeMemory(memory);
|
default:
|
||||||
};
|
UNREACHABLE();
|
||||||
|
|
||||||
// Schedule deletion of the texture after it's no longer used
|
|
||||||
// by the GPU
|
|
||||||
g_vk_task_scheduler->Schedule(deleter);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
VKTexture& VKTexture::operator=(VKTexture&& move) {
|
VKTexture::~VKTexture() {
|
||||||
|
Destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
void VKTexture::Create(const VKTexture::Info& create_info) {
|
void VKTexture::Create(const VKTexture::Info& create_info) {
|
||||||
auto& device = g_vk_instace->GetDevice();
|
auto& device = g_vk_instace->GetDevice();
|
||||||
info = create_info;
|
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
|
// Create the texture
|
||||||
image_size = info.width * info.height * channels;
|
image_size = info.width * info.height * BytesPerPixel(info.format);
|
||||||
|
|
||||||
vk::ImageCreateFlags flags{};
|
vk::ImageCreateFlags flags{};
|
||||||
if (info.view_type == vk::ImageViewType::eCube) {
|
if (info.view_type == vk::ImageViewType::eCube) {
|
||||||
@@ -71,7 +53,8 @@ void VKTexture::Create(const VKTexture::Info& create_info) {
|
|||||||
|
|
||||||
// Create texture memory
|
// Create texture memory
|
||||||
auto requirements = device.getImageMemoryRequirements(texture);
|
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);
|
vk::MemoryAllocateInfo alloc_info(requirements.size, memory_index);
|
||||||
|
|
||||||
memory = device.allocateMemory(alloc_info);
|
memory = device.allocateMemory(alloc_info);
|
||||||
@@ -86,6 +69,25 @@ void VKTexture::Create(const VKTexture::Info& create_info) {
|
|||||||
view = device.createImageView(view_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) {
|
void VKTexture::Transition(vk::ImageLayout new_layout) {
|
||||||
if (new_layout == layout) {
|
if (new_layout == layout) {
|
||||||
return;
|
return;
|
||||||
@@ -161,9 +163,8 @@ void VKTexture::Transition(vk::ImageLayout new_layout) {
|
|||||||
vk::ImageSubresourceRange(info.aspect, 0, 1, 0, 1)
|
vk::ImageSubresourceRange(info.aspect, 0, 1, 0, 1)
|
||||||
};
|
};
|
||||||
|
|
||||||
std::array<vk::ImageMemoryBarrier, 1> barriers{ barrier };
|
|
||||||
auto command_buffer = g_vk_task_scheduler->GetCommandBuffer();
|
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;
|
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!");
|
LOG_ERROR(Render_Vulkan, "Cannot upload pixels without staging buffer!");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto command_buffer = g_vk_task_scheduler->GetCommandBuffer();
|
|
||||||
|
|
||||||
// Copy pixels to staging buffer
|
// Copy pixels to staging buffer
|
||||||
|
auto command_buffer = g_vk_task_scheduler->GetCommandBuffer();
|
||||||
std::memcpy(buffer, pixels.data(), pixels.size());
|
std::memcpy(buffer, pixels.data(), pixels.size());
|
||||||
|
|
||||||
vk::BufferImageCopy copy_region{
|
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}
|
{region.extent.width, region.extent.height, 1}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Exit rendering for transfer operations
|
||||||
|
auto& state = VulkanState::Get();
|
||||||
|
state.EndRendering();
|
||||||
|
|
||||||
// Transition image to transfer format
|
// Transition image to transfer format
|
||||||
auto old_layout = GetLayout();
|
auto old_layout = GetLayout();
|
||||||
Transition(vk::ImageLayout::eTransferDstOptimal);
|
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}
|
{region.extent.width, region.extent.height, 1}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Exit rendering for transfer operations
|
||||||
|
auto& state = VulkanState::Get();
|
||||||
|
state.EndRendering();
|
||||||
|
|
||||||
// Transition image to transfer format
|
// Transition image to transfer format
|
||||||
auto old_layout = GetLayout();
|
auto old_layout = GetLayout();
|
||||||
Transition(vk::ImageLayout::eTransferSrcOptimal);
|
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
|
// Wait for the data to be available
|
||||||
// NOTE: This is really slow and should be reworked
|
// 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());
|
std::memcpy(memory.data(), buffer, memory.size_bytes());
|
||||||
|
|
||||||
// Restore layout
|
// Restore layout
|
||||||
|
@@ -14,12 +14,6 @@
|
|||||||
|
|
||||||
namespace Vulkan {
|
namespace Vulkan {
|
||||||
|
|
||||||
struct SamplerInfo {
|
|
||||||
std::array<vk::SamplerAddressMode, 3> wrapping{};
|
|
||||||
vk::Filter min_filter{}, mag_filter{};
|
|
||||||
vk::SamplerMipmapMode mipmap_mode{};
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Vulkan texture object
|
/// Vulkan texture object
|
||||||
class VKTexture final : public NonCopyable {
|
class VKTexture final : public NonCopyable {
|
||||||
public:
|
public:
|
||||||
@@ -33,7 +27,6 @@ public:
|
|||||||
vk::ImageAspectFlags aspect;
|
vk::ImageAspectFlags aspect;
|
||||||
u32 multisamples = 1;
|
u32 multisamples = 1;
|
||||||
u32 levels = 1, layers = 1;
|
u32 levels = 1, layers = 1;
|
||||||
SamplerInfo sampler_info = {};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
VKTexture() = default;
|
VKTexture() = default;
|
||||||
@@ -44,6 +37,8 @@ public:
|
|||||||
|
|
||||||
/// Create a new Vulkan texture object
|
/// Create a new Vulkan texture object
|
||||||
void Create(const VKTexture::Info& info);
|
void Create(const VKTexture::Info& info);
|
||||||
|
void Adopt
|
||||||
|
void Destroy();
|
||||||
|
|
||||||
/// Query objects
|
/// Query objects
|
||||||
bool IsValid() const { return texture; }
|
bool IsValid() const { return texture; }
|
||||||
@@ -68,7 +63,8 @@ private:
|
|||||||
vk::Image texture;
|
vk::Image texture;
|
||||||
vk::ImageView view;
|
vk::ImageView view;
|
||||||
vk::DeviceMemory memory;
|
vk::DeviceMemory memory;
|
||||||
u32 channels{}, image_size{};
|
u32 image_size{};
|
||||||
|
bool adopted = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Vulkan
|
} // namespace Vulkan
|
||||||
|
Reference in New Issue
Block a user