Compare commits
4 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
287db66914 | ||
|
22dea3c194 | ||
|
b7b458e4be | ||
|
ed699f1e2b |
@ -103,8 +103,8 @@ add_library(video_core STATIC
|
||||
renderer_vulkan/vk_blit_helper.h
|
||||
renderer_vulkan/vk_common.cpp
|
||||
renderer_vulkan/vk_common.h
|
||||
renderer_vulkan/vk_descriptor_pool.cpp
|
||||
renderer_vulkan/vk_descriptor_pool.h
|
||||
renderer_vulkan/vk_descriptor_update.cpp
|
||||
renderer_vulkan/vk_descriptor_update.h
|
||||
renderer_vulkan/vk_graphics_pipeline.cpp
|
||||
renderer_vulkan/vk_graphics_pipeline.h
|
||||
renderer_vulkan/vk_master_semaphore.cpp
|
||||
|
@ -176,15 +176,23 @@ struct TexturingRegs {
|
||||
INSERT_PADDING_WORDS(0x9);
|
||||
|
||||
struct FullTextureConfig {
|
||||
const bool enabled;
|
||||
const TextureConfig config;
|
||||
const TextureFormat format;
|
||||
u32 enabled;
|
||||
TextureConfig config;
|
||||
TextureFormat format;
|
||||
|
||||
bool operator==(const FullTextureConfig& other) const noexcept {
|
||||
return std::memcmp(this, &other, sizeof(other)) == 0;
|
||||
}
|
||||
};
|
||||
const std::array<FullTextureConfig, 3> GetTextures() const {
|
||||
static_assert(std::has_unique_object_representations_v<FullTextureConfig>);
|
||||
|
||||
using Textures = std::array<FullTextureConfig, 3>;
|
||||
|
||||
const Textures GetTextures() const {
|
||||
return {{
|
||||
{static_cast<bool>(main_config.texture0_enable), texture0, texture0_format},
|
||||
{static_cast<bool>(main_config.texture1_enable), texture1, texture1_format},
|
||||
{static_cast<bool>(main_config.texture2_enable), texture2, texture2_format},
|
||||
{main_config.texture0_enable, texture0, texture0_format},
|
||||
{main_config.texture1_enable, texture1, texture1_format},
|
||||
{main_config.texture2_enable, texture2, texture2_format},
|
||||
}};
|
||||
}
|
||||
|
||||
@ -381,11 +389,11 @@ struct TexturingRegs {
|
||||
BitField<16, 2, u32> alpha_scale;
|
||||
};
|
||||
|
||||
inline unsigned GetColorMultiplier() const {
|
||||
inline u32 GetColorMultiplier() const {
|
||||
return (color_scale < 3) ? (1 << color_scale) : 1;
|
||||
}
|
||||
|
||||
inline unsigned GetAlphaMultiplier() const {
|
||||
inline u32 GetAlphaMultiplier() const {
|
||||
return (alpha_scale < 3) ? (1 << alpha_scale) : 1;
|
||||
}
|
||||
};
|
||||
|
@ -14,7 +14,7 @@
|
||||
#include "video_core/renderer_opengl/gl_rasterizer.h"
|
||||
#include "video_core/renderer_opengl/pica_to_gl.h"
|
||||
#include "video_core/renderer_opengl/renderer_opengl.h"
|
||||
#include "video_core/shader/generator/glsl_shader_gen.h"
|
||||
#include "video_core/shader/generator/shader_gen.h"
|
||||
#include "video_core/texture/texture_decode.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
@ -576,10 +576,9 @@ void RasterizerOpenGL::BindTextureCube(const Pica::TexturingRegs::FullTextureCon
|
||||
|
||||
Surface& surface = res_cache.GetTextureCube(config);
|
||||
Sampler& sampler = res_cache.GetSampler(texture.config);
|
||||
|
||||
state.texture_cube_unit.texture_cube = surface.Handle();
|
||||
state.texture_cube_unit.sampler = sampler.Handle();
|
||||
state.texture_units[0].texture_2d = 0;
|
||||
state.texture_units[0].target = GL_TEXTURE_CUBE_MAP;
|
||||
state.texture_units[0].texture_2d = surface.Handle();
|
||||
state.texture_units[0].sampler = sampler.Handle();
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::BindMaterial(u32 texture_index, Surface& surface) {
|
||||
@ -612,7 +611,8 @@ bool RasterizerOpenGL::IsFeedbackLoop(u32 texture_index, const Framebuffer* fram
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::UnbindSpecial() {
|
||||
state.texture_cube_unit.texture_cube = 0;
|
||||
state.texture_units[0].texture_2d = 0;
|
||||
state.texture_units[0].target = GL_TEXTURE_2D;
|
||||
state.image_shadow_texture_px = 0;
|
||||
state.image_shadow_texture_nx = 0;
|
||||
state.image_shadow_texture_py = 0;
|
||||
|
@ -50,11 +50,11 @@ OpenGLState::OpenGLState() {
|
||||
|
||||
for (auto& texture_unit : texture_units) {
|
||||
texture_unit.texture_2d = 0;
|
||||
texture_unit.target = GL_TEXTURE_2D;
|
||||
texture_unit.sampler = 0;
|
||||
}
|
||||
|
||||
texture_cube_unit.texture_cube = 0;
|
||||
texture_cube_unit.sampler = 0;
|
||||
color_buffer.texture_2d = 0;
|
||||
|
||||
texture_buffer_lut_lf.texture_buffer = 0;
|
||||
texture_buffer_lut_rg.texture_buffer = 0;
|
||||
@ -213,21 +213,13 @@ void OpenGLState::Apply() const {
|
||||
for (u32 i = 0; i < texture_units.size(); ++i) {
|
||||
if (texture_units[i].texture_2d != cur_state.texture_units[i].texture_2d) {
|
||||
glActiveTexture(TextureUnits::PicaTexture(i).Enum());
|
||||
glBindTexture(GL_TEXTURE_2D, texture_units[i].texture_2d);
|
||||
glBindTexture(texture_units[i].target, texture_units[i].texture_2d);
|
||||
}
|
||||
if (texture_units[i].sampler != cur_state.texture_units[i].sampler) {
|
||||
glBindSampler(i, texture_units[i].sampler);
|
||||
}
|
||||
}
|
||||
|
||||
if (texture_cube_unit.texture_cube != cur_state.texture_cube_unit.texture_cube) {
|
||||
glActiveTexture(TextureUnits::TextureCube.Enum());
|
||||
glBindTexture(GL_TEXTURE_CUBE_MAP, texture_cube_unit.texture_cube);
|
||||
}
|
||||
if (texture_cube_unit.sampler != cur_state.texture_cube_unit.sampler) {
|
||||
glBindSampler(TextureUnits::TextureCube.id, texture_cube_unit.sampler);
|
||||
}
|
||||
|
||||
// Texture buffer LUTs
|
||||
if (texture_buffer_lut_lf.texture_buffer != cur_state.texture_buffer_lut_lf.texture_buffer) {
|
||||
glActiveTexture(TextureUnits::TextureBufferLUT_LF.Enum());
|
||||
@ -368,8 +360,6 @@ OpenGLState& OpenGLState::ResetTexture(GLuint handle) {
|
||||
unit.texture_2d = 0;
|
||||
}
|
||||
}
|
||||
if (texture_cube_unit.texture_cube == handle)
|
||||
texture_cube_unit.texture_cube = 0;
|
||||
if (texture_buffer_lut_lf.texture_buffer == handle)
|
||||
texture_buffer_lut_lf.texture_buffer = 0;
|
||||
if (texture_buffer_lut_rg.texture_buffer == handle)
|
||||
@ -399,9 +389,6 @@ OpenGLState& OpenGLState::ResetSampler(GLuint handle) {
|
||||
unit.sampler = 0;
|
||||
}
|
||||
}
|
||||
if (texture_cube_unit.sampler == handle) {
|
||||
texture_cube_unit.sampler = 0;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
@ -22,12 +22,11 @@ constexpr TextureUnit PicaTexture(int unit) {
|
||||
return TextureUnit{unit};
|
||||
}
|
||||
|
||||
constexpr TextureUnit TextureCube{6};
|
||||
constexpr TextureUnit TextureBufferLUT_LF{3};
|
||||
constexpr TextureUnit TextureBufferLUT_RG{4};
|
||||
constexpr TextureUnit TextureBufferLUT_RGBA{5};
|
||||
constexpr TextureUnit TextureNormalMap{7};
|
||||
constexpr TextureUnit TextureColorBuffer{10};
|
||||
constexpr TextureUnit TextureNormalMap{6};
|
||||
constexpr TextureUnit TextureColorBuffer{7};
|
||||
|
||||
} // namespace TextureUnits
|
||||
|
||||
@ -95,15 +94,11 @@ public:
|
||||
// 3 texture units - one for each that is used in PICA fragment shader emulation
|
||||
struct TextureUnit {
|
||||
GLuint texture_2d; // GL_TEXTURE_BINDING_2D
|
||||
GLenum target; // GL_TEXTURE_TARGET
|
||||
GLuint sampler; // GL_SAMPLER_BINDING
|
||||
};
|
||||
std::array<TextureUnit, 3> texture_units;
|
||||
|
||||
struct {
|
||||
GLuint texture_cube; // GL_TEXTURE_BINDING_CUBE_MAP
|
||||
GLuint sampler; // GL_SAMPLER_BINDING
|
||||
} texture_cube_unit;
|
||||
|
||||
struct {
|
||||
GLuint texture_buffer; // GL_TEXTURE_BINDING_BUFFER
|
||||
} texture_buffer_lut_lf;
|
||||
|
@ -7,10 +7,8 @@
|
||||
#include "common/memory_detect.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/texture.h"
|
||||
#include "core/core.h"
|
||||
#include "core/frontend/emu_window.h"
|
||||
#include "core/hw/gpu.h"
|
||||
#include "core/hw/hw.h"
|
||||
#include "core/hw/lcd.h"
|
||||
#include "video_core/renderer_vulkan/renderer_vulkan.h"
|
||||
@ -21,7 +19,6 @@
|
||||
#include "video_core/host_shaders/vulkan_present_frag_spv.h"
|
||||
#include "video_core/host_shaders/vulkan_present_interlaced_frag_spv.h"
|
||||
#include "video_core/host_shaders/vulkan_present_vert_spv.h"
|
||||
#include "vulkan/vulkan_format_traits.hpp"
|
||||
|
||||
#include <vk_mem_alloc.h>
|
||||
|
||||
@ -38,6 +35,7 @@ struct ScreenRectVertex {
|
||||
Common::Vec2f tex_coord;
|
||||
};
|
||||
|
||||
constexpr u32 MAX_IN_FLIGHT_FRAMES = 10;
|
||||
constexpr u32 VERTEX_BUFFER_SIZE = sizeof(ScreenRectVertex) * 8192;
|
||||
|
||||
constexpr std::array<f32, 4 * 4> MakeOrthographicMatrix(u32 width, u32 height) {
|
||||
@ -49,16 +47,12 @@ constexpr std::array<f32, 4 * 4> MakeOrthographicMatrix(u32 width, u32 height) {
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
constexpr static std::array<vk::DescriptorSetLayoutBinding, 1> PRESENT_BINDINGS = {{
|
||||
{0, vk::DescriptorType::eCombinedImageSampler, 3, vk::ShaderStageFlagBits::eFragment},
|
||||
}};
|
||||
|
||||
RendererVulkan::RendererVulkan(Core::System& system, Frontend::EmuWindow& window,
|
||||
Frontend::EmuWindow* secondary_window)
|
||||
: RendererBase{system, window, secondary_window}, memory{system.Memory()},
|
||||
instance{system.TelemetrySession(), window, Settings::values.physical_device.GetValue()},
|
||||
scheduler{instance, renderpass_cache}, renderpass_cache{instance, scheduler}, pool{instance},
|
||||
main_window{window, instance, scheduler},
|
||||
scheduler{instance, renderpass_cache}, renderpass_cache{instance, scheduler},
|
||||
pool{instance, scheduler.GetMasterSemaphore()}, main_window{window, instance, scheduler},
|
||||
vertex_buffer{instance, scheduler, vk::BufferUsageFlagBits::eVertexBuffer,
|
||||
VERTEX_BUFFER_SIZE},
|
||||
rasterizer{memory,
|
||||
@ -69,10 +63,9 @@ RendererVulkan::RendererVulkan(Core::System& system, Frontend::EmuWindow& window
|
||||
scheduler,
|
||||
pool,
|
||||
renderpass_cache,
|
||||
main_window.ImageCount()},
|
||||
present_set_provider{instance, pool, PRESENT_BINDINGS} {
|
||||
main_window.ImageCount()} {
|
||||
CompileShaders();
|
||||
BuildLayouts();
|
||||
BuildLayoutsAndDescriptors();
|
||||
BuildPipelines();
|
||||
if (secondary_window) {
|
||||
second_window = std::make_unique<PresentWindow>(*secondary_window, instance, scheduler);
|
||||
@ -80,17 +73,21 @@ RendererVulkan::RendererVulkan(Core::System& system, Frontend::EmuWindow& window
|
||||
}
|
||||
|
||||
RendererVulkan::~RendererVulkan() {
|
||||
vk::Device device = instance.GetDevice();
|
||||
const vk::Device device = instance.GetDevice();
|
||||
scheduler.Finish();
|
||||
device.waitIdle();
|
||||
|
||||
device.destroyShaderModule(present_vertex_shader);
|
||||
for (u32 i = 0; i < PRESENT_PIPELINES; i++) {
|
||||
device.destroyPipeline(present_pipelines[i]);
|
||||
device.destroyShaderModule(present_shaders[i]);
|
||||
device.destroyPipelineLayout(pipeline_layout);
|
||||
device.destroyDescriptorSetLayout(descriptor_set_layout);
|
||||
device.destroyDescriptorUpdateTemplate(update_template);
|
||||
device.destroyShaderModule(vert_shader);
|
||||
|
||||
for (u32 i = 0; i < NUM_PIPELINES; i++) {
|
||||
device.destroyPipeline(pipelines[i]);
|
||||
device.destroyShaderModule(frag_shaders[i]);
|
||||
}
|
||||
|
||||
for (auto& sampler : present_samplers) {
|
||||
for (auto sampler : samplers) {
|
||||
device.destroySampler(sampler);
|
||||
}
|
||||
|
||||
@ -105,9 +102,10 @@ void RendererVulkan::Sync() {
|
||||
}
|
||||
|
||||
void RendererVulkan::PrepareRendertarget() {
|
||||
for (u32 i = 0; i < 3; i++) {
|
||||
for (u32 i = 0; i < NUM_SCREENS; i++) {
|
||||
const u32 fb_id = i == 2 ? 1 : 0;
|
||||
const auto& framebuffer = GPU::g_regs.framebuffer_config[fb_id];
|
||||
auto& texture = screen_infos[i].texture;
|
||||
|
||||
// Main LCD (0): 0x1ED02204, Sub LCD (1): 0x1ED02A04
|
||||
u32 lcd_color_addr =
|
||||
@ -118,37 +116,31 @@ void RendererVulkan::PrepareRendertarget() {
|
||||
|
||||
if (color_fill.is_enabled) {
|
||||
LoadColorToActiveVkTexture(color_fill.color_r, color_fill.color_g, color_fill.color_b,
|
||||
screen_infos[i].texture);
|
||||
} else {
|
||||
TextureInfo& texture = screen_infos[i].texture;
|
||||
if (texture.width != framebuffer.width || texture.height != framebuffer.height ||
|
||||
texture.format != framebuffer.color_format) {
|
||||
|
||||
// Reallocate texture if the framebuffer size has changed.
|
||||
// This is expected to not happen very often and hence should not be a
|
||||
// performance problem.
|
||||
ConfigureFramebufferTexture(texture, framebuffer);
|
||||
}
|
||||
|
||||
LoadFBToScreenInfo(framebuffer, screen_infos[i], i == 1);
|
||||
|
||||
// Resize the texture in case the framebuffer size has changed
|
||||
texture.width = framebuffer.width;
|
||||
texture.height = framebuffer.height;
|
||||
texture);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (texture.width != framebuffer.width || texture.height != framebuffer.height ||
|
||||
texture.format != framebuffer.color_format) {
|
||||
ConfigureFramebufferTexture(texture, framebuffer);
|
||||
}
|
||||
|
||||
LoadFBToScreenInfo(framebuffer, screen_infos[i], i == 1);
|
||||
}
|
||||
}
|
||||
|
||||
void RendererVulkan::PrepareDraw(Frame* frame, const Layout::FramebufferLayout& layout) {
|
||||
const auto sampler = present_samplers[!Settings::values.filter_mode.GetValue()];
|
||||
std::transform(screen_infos.begin(), screen_infos.end(), present_textures.begin(),
|
||||
[&](auto& info) {
|
||||
return DescriptorData{vk::DescriptorImageInfo{sampler, info.image_view,
|
||||
vk::ImageLayout::eGeneral}};
|
||||
});
|
||||
const auto sampler = samplers[!Settings::values.filter_mode.GetValue()];
|
||||
std::transform(screen_infos.begin(), screen_infos.end(), image_infos.begin(), [&](auto& info) {
|
||||
return vk::DescriptorImageInfo{sampler, info.image_view, vk::ImageLayout::eGeneral};
|
||||
});
|
||||
|
||||
const auto descriptor_set = present_set_provider.Acquire(present_textures);
|
||||
// Prepare the descriptor set with presentation images.
|
||||
const auto descriptor_set = present_sets[frame->index];
|
||||
const vk::Device device = instance.GetDevice();
|
||||
device.updateDescriptorSetWithTemplate(descriptor_set, update_template, image_infos);
|
||||
|
||||
// Bind presentation pipeline, enter renderpass.
|
||||
renderpass_cache.EndRendering();
|
||||
scheduler.Record([this, layout, frame, descriptor_set, renderpass = main_window.Renderpass(),
|
||||
index = current_pipeline](vk::CommandBuffer cmdbuf) {
|
||||
@ -170,12 +162,11 @@ void RendererVulkan::PrepareDraw(Frame* frame, const Layout::FramebufferLayout&
|
||||
cmdbuf.setScissor(0, scissor);
|
||||
|
||||
const vk::ClearValue clear{.color = clear_color};
|
||||
const vk::PipelineLayout layout{*present_pipeline_layout};
|
||||
const vk::RenderPassBeginInfo renderpass_begin_info = {
|
||||
.renderPass = renderpass,
|
||||
.framebuffer = frame->framebuffer,
|
||||
.renderArea =
|
||||
vk::Rect2D{
|
||||
{
|
||||
.offset = {0, 0},
|
||||
.extent = {frame->width, frame->height},
|
||||
},
|
||||
@ -184,8 +175,9 @@ void RendererVulkan::PrepareDraw(Frame* frame, const Layout::FramebufferLayout&
|
||||
};
|
||||
|
||||
cmdbuf.beginRenderPass(renderpass_begin_info, vk::SubpassContents::eInline);
|
||||
cmdbuf.bindPipeline(vk::PipelineBindPoint::eGraphics, present_pipelines[index]);
|
||||
cmdbuf.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, layout, 0, descriptor_set, {});
|
||||
cmdbuf.bindPipeline(vk::PipelineBindPoint::eGraphics, pipelines[index]);
|
||||
cmdbuf.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipeline_layout, 0,
|
||||
descriptor_set, {});
|
||||
});
|
||||
}
|
||||
|
||||
@ -239,13 +231,13 @@ void RendererVulkan::LoadFBToScreenInfo(const GPU::Regs::FramebufferConfig& fram
|
||||
|
||||
void RendererVulkan::CompileShaders() {
|
||||
vk::Device device = instance.GetDevice();
|
||||
present_vertex_shader = CompileSPV(VULKAN_PRESENT_VERT_SPV, device);
|
||||
present_shaders[0] = CompileSPV(VULKAN_PRESENT_FRAG_SPV, device);
|
||||
present_shaders[1] = CompileSPV(VULKAN_PRESENT_ANAGLYPH_FRAG_SPV, device);
|
||||
present_shaders[2] = CompileSPV(VULKAN_PRESENT_INTERLACED_FRAG_SPV, device);
|
||||
vert_shader = CompileSPV(VULKAN_PRESENT_VERT_SPV, device);
|
||||
frag_shaders[0] = CompileSPV(VULKAN_PRESENT_FRAG_SPV, device);
|
||||
frag_shaders[1] = CompileSPV(VULKAN_PRESENT_ANAGLYPH_FRAG_SPV, device);
|
||||
frag_shaders[2] = CompileSPV(VULKAN_PRESENT_INTERLACED_FRAG_SPV, device);
|
||||
|
||||
auto properties = instance.GetPhysicalDevice().getProperties();
|
||||
for (std::size_t i = 0; i < present_samplers.size(); i++) {
|
||||
const auto properties = instance.GetPhysicalDevice().getProperties();
|
||||
for (std::size_t i = 0; i < samplers.size(); i++) {
|
||||
const vk::Filter filter_mode = i == 0 ? vk::Filter::eLinear : vk::Filter::eNearest;
|
||||
const vk::SamplerCreateInfo sampler_info = {
|
||||
.magFilter = filter_mode,
|
||||
@ -261,25 +253,59 @@ void RendererVulkan::CompileShaders() {
|
||||
.unnormalizedCoordinates = false,
|
||||
};
|
||||
|
||||
present_samplers[i] = device.createSampler(sampler_info);
|
||||
samplers[i] = device.createSampler(sampler_info);
|
||||
}
|
||||
}
|
||||
|
||||
void RendererVulkan::BuildLayouts() {
|
||||
void RendererVulkan::BuildLayoutsAndDescriptors() {
|
||||
const vk::PushConstantRange push_range = {
|
||||
.stageFlags = vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment,
|
||||
.offset = 0,
|
||||
.size = sizeof(PresentUniformData),
|
||||
};
|
||||
|
||||
const auto descriptor_set_layout = present_set_provider.Layout();
|
||||
const vk::PipelineLayoutCreateInfo layout_info = {
|
||||
const vk::DescriptorSetLayoutBinding binding = {
|
||||
.binding = 0,
|
||||
.descriptorType = vk::DescriptorType::eCombinedImageSampler,
|
||||
.descriptorCount = 3,
|
||||
.stageFlags = vk::ShaderStageFlagBits::eFragment,
|
||||
};
|
||||
|
||||
const vk::DescriptorUpdateTemplateEntry update_entry = {
|
||||
.dstBinding = 0,
|
||||
.dstArrayElement = 0,
|
||||
.descriptorCount = 3,
|
||||
.descriptorType = vk::DescriptorType::eCombinedImageSampler,
|
||||
.offset = 0,
|
||||
.stride = sizeof(vk::DescriptorImageInfo),
|
||||
};
|
||||
|
||||
const vk::DescriptorSetLayoutCreateInfo layout_info = {
|
||||
.bindingCount = 1,
|
||||
.pBindings = &binding,
|
||||
};
|
||||
|
||||
const vk::Device device = instance.GetDevice();
|
||||
descriptor_set_layout = device.createDescriptorSetLayout(layout_info);
|
||||
|
||||
const vk::DescriptorUpdateTemplateCreateInfo template_info = {
|
||||
.descriptorUpdateEntryCount = 1,
|
||||
.pDescriptorUpdateEntries = &update_entry,
|
||||
.templateType = vk::DescriptorUpdateTemplateType::eDescriptorSet,
|
||||
.descriptorSetLayout = descriptor_set_layout,
|
||||
};
|
||||
update_template = device.createDescriptorUpdateTemplate(template_info);
|
||||
|
||||
const vk::PipelineLayoutCreateInfo pipeline_layout_info = {
|
||||
.setLayoutCount = 1,
|
||||
.pSetLayouts = &descriptor_set_layout,
|
||||
.pushConstantRangeCount = 1,
|
||||
.pPushConstantRanges = &push_range,
|
||||
};
|
||||
present_pipeline_layout = instance.GetDevice().createPipelineLayoutUnique(layout_info);
|
||||
pipeline_layout = device.createPipelineLayout(pipeline_layout_info);
|
||||
|
||||
const u32 image_count = main_window.ImageCount();
|
||||
present_sets = pool.Commit(descriptor_set_layout, image_count);
|
||||
}
|
||||
|
||||
void RendererVulkan::BuildPipelines() {
|
||||
@ -370,16 +396,16 @@ void RendererVulkan::BuildPipelines() {
|
||||
.stencilTestEnable = false,
|
||||
};
|
||||
|
||||
for (u32 i = 0; i < PRESENT_PIPELINES; i++) {
|
||||
for (u32 i = 0; i < NUM_PIPELINES; i++) {
|
||||
const std::array shader_stages = {
|
||||
vk::PipelineShaderStageCreateInfo{
|
||||
.stage = vk::ShaderStageFlagBits::eVertex,
|
||||
.module = present_vertex_shader,
|
||||
.module = vert_shader,
|
||||
.pName = "main",
|
||||
},
|
||||
vk::PipelineShaderStageCreateInfo{
|
||||
.stage = vk::ShaderStageFlagBits::eFragment,
|
||||
.module = present_shaders[i],
|
||||
.module = frag_shaders[i],
|
||||
.pName = "main",
|
||||
},
|
||||
};
|
||||
@ -395,14 +421,14 @@ void RendererVulkan::BuildPipelines() {
|
||||
.pDepthStencilState = &depth_info,
|
||||
.pColorBlendState = &color_blending,
|
||||
.pDynamicState = &dynamic_info,
|
||||
.layout = *present_pipeline_layout,
|
||||
.layout = pipeline_layout,
|
||||
.renderPass = main_window.Renderpass(),
|
||||
};
|
||||
|
||||
const auto [result, pipeline] =
|
||||
instance.GetDevice().createGraphicsPipeline({}, pipeline_info);
|
||||
ASSERT_MSG(result == vk::Result::eSuccess, "Unable to build present pipelines");
|
||||
present_pipelines[i] = pipeline;
|
||||
pipelines[i] = pipeline;
|
||||
}
|
||||
}
|
||||
|
||||
@ -416,8 +442,7 @@ void RendererVulkan::ConfigureFramebufferTexture(TextureInfo& texture,
|
||||
vmaDestroyImage(instance.GetAllocator(), texture.image, texture.allocation);
|
||||
}
|
||||
|
||||
const VideoCore::PixelFormat pixel_format =
|
||||
VideoCore::PixelFormatFromGPUPixelFormat(framebuffer.color_format);
|
||||
const auto pixel_format = VideoCore::PixelFormatFromGPUPixelFormat(framebuffer.color_format);
|
||||
const vk::Format format = instance.GetTraits(pixel_format).native;
|
||||
const vk::ImageCreateInfo image_info = {
|
||||
.imageType = vk::ImageType::e2D,
|
||||
@ -603,7 +628,7 @@ void RendererVulkan::DrawSingleScreen(u32 screen_id, float x, float y, float w,
|
||||
|
||||
scheduler.Record([this, offset = offset, info = draw_info](vk::CommandBuffer cmdbuf) {
|
||||
const u32 first_vertex = static_cast<u32>(offset) / sizeof(ScreenRectVertex);
|
||||
cmdbuf.pushConstants(*present_pipeline_layout,
|
||||
cmdbuf.pushConstants(pipeline_layout,
|
||||
vk::ShaderStageFlagBits::eFragment | vk::ShaderStageFlagBits::eVertex,
|
||||
0, sizeof(info), &info);
|
||||
|
||||
@ -676,7 +701,7 @@ void RendererVulkan::DrawSingleScreenStereo(u32 screen_id_l, u32 screen_id_r, fl
|
||||
|
||||
scheduler.Record([this, offset = offset, info = draw_info](vk::CommandBuffer cmdbuf) {
|
||||
const u32 first_vertex = static_cast<u32>(offset) / sizeof(ScreenRectVertex);
|
||||
cmdbuf.pushConstants(*present_pipeline_layout,
|
||||
cmdbuf.pushConstants(pipeline_layout,
|
||||
vk::ShaderStageFlagBits::eFragment | vk::ShaderStageFlagBits::eVertex,
|
||||
0, sizeof(info), &info);
|
||||
|
||||
@ -817,29 +842,7 @@ void RendererVulkan::DrawScreens(Frame* frame, const Layout::FramebufferLayout&
|
||||
}
|
||||
}
|
||||
|
||||
scheduler.Record([image = frame->image](vk::CommandBuffer cmdbuf) {
|
||||
const vk::ImageMemoryBarrier render_barrier = {
|
||||
.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite,
|
||||
.dstAccessMask = vk::AccessFlagBits::eTransferRead,
|
||||
.oldLayout = vk::ImageLayout::eTransferSrcOptimal,
|
||||
.newLayout = vk::ImageLayout::eTransferSrcOptimal,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = image,
|
||||
.subresourceRange{
|
||||
.aspectMask = vk::ImageAspectFlagBits::eColor,
|
||||
.baseMipLevel = 0,
|
||||
.levelCount = 1,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = VK_REMAINING_ARRAY_LAYERS,
|
||||
},
|
||||
};
|
||||
|
||||
cmdbuf.endRenderPass();
|
||||
cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eColorAttachmentOutput,
|
||||
vk::PipelineStageFlagBits::eTransfer,
|
||||
vk::DependencyFlagBits::eByRegion, {}, {}, render_barrier);
|
||||
});
|
||||
scheduler.Record([](vk::CommandBuffer cmdbuf) { cmdbuf.endRenderPass(); });
|
||||
}
|
||||
|
||||
void RendererVulkan::SwapBuffers() {
|
||||
@ -1032,9 +1035,8 @@ bool RendererVulkan::TryRenderScreenshotWithHostMemory() {
|
||||
.imageExtent = {width, height, 1},
|
||||
};
|
||||
|
||||
const vk::MemoryHostPointerPropertiesEXT import_properties =
|
||||
device.getMemoryHostPointerPropertiesEXT(
|
||||
vk::ExternalMemoryHandleTypeFlagBits::eHostAllocationEXT, aligned_pointer);
|
||||
const auto import_properties = device.getMemoryHostPointerPropertiesEXT(
|
||||
vk::ExternalMemoryHandleTypeFlagBits::eHostAllocationEXT, aligned_pointer);
|
||||
|
||||
if (!import_properties.memoryTypeBits) {
|
||||
// Could not import memory
|
||||
@ -1051,33 +1053,31 @@ bool RendererVulkan::TryRenderScreenshotWithHostMemory() {
|
||||
return false;
|
||||
}
|
||||
|
||||
const vk::StructureChain<vk::MemoryAllocateInfo, vk::ImportMemoryHostPointerInfoEXT>
|
||||
allocation_chain = {
|
||||
vk::MemoryAllocateInfo{
|
||||
.allocationSize = aligned_size,
|
||||
.memoryTypeIndex = memory_type_index.value(),
|
||||
},
|
||||
vk::ImportMemoryHostPointerInfoEXT{
|
||||
.handleType = vk::ExternalMemoryHandleTypeFlagBits::eHostAllocationEXT,
|
||||
.pHostPointer = aligned_pointer,
|
||||
},
|
||||
};
|
||||
const vk::StructureChain allocation_chain = {
|
||||
vk::MemoryAllocateInfo{
|
||||
.allocationSize = aligned_size,
|
||||
.memoryTypeIndex = memory_type_index.value(),
|
||||
},
|
||||
vk::ImportMemoryHostPointerInfoEXT{
|
||||
.handleType = vk::ExternalMemoryHandleTypeFlagBits::eHostAllocationEXT,
|
||||
.pHostPointer = aligned_pointer,
|
||||
},
|
||||
};
|
||||
|
||||
// Import host memory
|
||||
const vk::UniqueDeviceMemory imported_memory =
|
||||
device.allocateMemoryUnique(allocation_chain.get());
|
||||
|
||||
const vk::StructureChain<vk::BufferCreateInfo, vk::ExternalMemoryBufferCreateInfo> buffer_info =
|
||||
{
|
||||
vk::BufferCreateInfo{
|
||||
.size = aligned_size,
|
||||
.usage = vk::BufferUsageFlagBits::eTransferDst,
|
||||
.sharingMode = vk::SharingMode::eExclusive,
|
||||
},
|
||||
vk::ExternalMemoryBufferCreateInfo{
|
||||
.handleTypes = vk::ExternalMemoryHandleTypeFlagBits::eHostAllocationEXT,
|
||||
},
|
||||
};
|
||||
const vk::StructureChain buffer_info = {
|
||||
vk::BufferCreateInfo{
|
||||
.size = aligned_size,
|
||||
.usage = vk::BufferUsageFlagBits::eTransferDst,
|
||||
.sharingMode = vk::SharingMode::eExclusive,
|
||||
},
|
||||
vk::ExternalMemoryBufferCreateInfo{
|
||||
.handleTypes = vk::ExternalMemoryHandleTypeFlagBits::eHostAllocationEXT,
|
||||
},
|
||||
};
|
||||
|
||||
// Bind imported memory to buffer
|
||||
const vk::UniqueBuffer imported_buffer = device.createBufferUnique(buffer_info.get());
|
||||
|
@ -4,20 +4,15 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include "common/common_types.h"
|
||||
#include "common/math_util.h"
|
||||
#include "core/hw/gpu.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
|
||||
#include "video_core/renderer_vulkan/vk_instance.h"
|
||||
#include "video_core/renderer_vulkan/vk_present_window.h"
|
||||
#include "video_core/renderer_vulkan/vk_rasterizer.h"
|
||||
#include "video_core/renderer_vulkan/vk_renderpass_cache.h"
|
||||
#include "video_core/renderer_vulkan/vk_resource_pool.h"
|
||||
#include "video_core/renderer_vulkan/vk_scheduler.h"
|
||||
#include "video_core/renderer_vulkan/vk_swapchain.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
@ -61,7 +56,8 @@ static_assert(sizeof(PresentUniformData) == 112,
|
||||
"PresentUniformData does not structure in shader!");
|
||||
|
||||
class RendererVulkan : public VideoCore::RendererBase {
|
||||
static constexpr std::size_t PRESENT_PIPELINES = 3;
|
||||
static constexpr std::size_t NUM_PIPELINES = 3;
|
||||
static constexpr std::size_t NUM_SCREENS = 3;
|
||||
|
||||
public:
|
||||
explicit RendererVulkan(Core::System& system, Frontend::EmuWindow& window,
|
||||
@ -83,7 +79,7 @@ public:
|
||||
private:
|
||||
void ReloadPipeline();
|
||||
void CompileShaders();
|
||||
void BuildLayouts();
|
||||
void BuildLayoutsAndDescriptors();
|
||||
void BuildPipelines();
|
||||
void ConfigureFramebufferTexture(TextureInfo& texture,
|
||||
const GPU::Regs::FramebufferConfig& framebuffer);
|
||||
@ -121,16 +117,18 @@ private:
|
||||
RasterizerVulkan rasterizer;
|
||||
std::unique_ptr<PresentWindow> second_window;
|
||||
|
||||
vk::UniquePipelineLayout present_pipeline_layout;
|
||||
DescriptorSetProvider present_set_provider;
|
||||
std::array<vk::Pipeline, PRESENT_PIPELINES> present_pipelines;
|
||||
std::array<vk::ShaderModule, PRESENT_PIPELINES> present_shaders;
|
||||
std::array<vk::Sampler, 2> present_samplers;
|
||||
vk::ShaderModule present_vertex_shader;
|
||||
vk::PipelineLayout pipeline_layout;
|
||||
vk::DescriptorSetLayout descriptor_set_layout;
|
||||
vk::DescriptorUpdateTemplate update_template;
|
||||
std::vector<vk::DescriptorSet> present_sets;
|
||||
std::array<vk::Pipeline, NUM_PIPELINES> pipelines;
|
||||
std::array<vk::ShaderModule, NUM_PIPELINES> frag_shaders;
|
||||
std::array<vk::Sampler, 2> samplers;
|
||||
vk::ShaderModule vert_shader;
|
||||
u32 current_pipeline = 0;
|
||||
|
||||
std::array<ScreenInfo, 3> screen_infos{};
|
||||
std::array<DescriptorData, 3> present_textures{};
|
||||
std::array<ScreenInfo, NUM_SCREENS> screen_infos{};
|
||||
std::array<vk::DescriptorImageInfo, NUM_SCREENS> image_infos{};
|
||||
PresentUniformData draw_info{};
|
||||
vk::ClearColorValue clear_color{};
|
||||
};
|
||||
|
@ -177,10 +177,9 @@ constexpr vk::PipelineShaderStageCreateInfo MakeStages(vk::ShaderModule compute_
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
BlitHelper::BlitHelper(const Instance& instance_, Scheduler& scheduler_, DescriptorPool& pool,
|
||||
RenderpassCache& renderpass_cache_)
|
||||
: instance{instance_}, scheduler{scheduler_}, renderpass_cache{renderpass_cache_},
|
||||
device{instance.GetDevice()}, compute_provider{instance, pool, COMPUTE_BINDINGS},
|
||||
BlitHelper::BlitHelper(const Instance& instance_, Scheduler& scheduler_)
|
||||
: instance{instance_}, scheduler{scheduler_}, device{instance.GetDevice()},
|
||||
compute_provider{instance, pool, COMPUTE_BINDINGS},
|
||||
compute_buffer_provider{instance, pool, COMPUTE_BUFFER_BINDINGS},
|
||||
two_textures_provider{instance, pool, TWO_TEXTURES_BINDINGS},
|
||||
compute_pipeline_layout{
|
||||
@ -314,7 +313,6 @@ bool BlitHelper::ConvertDS24S8ToRGBA8(Surface& source, Surface& dest,
|
||||
|
||||
const auto descriptor_set = compute_provider.Acquire(textures);
|
||||
|
||||
renderpass_cache.EndRendering();
|
||||
scheduler.Record([this, descriptor_set, copy, src_image = source.Image(),
|
||||
dst_image = dest.Image()](vk::CommandBuffer cmdbuf) {
|
||||
const std::array pre_barriers = {
|
||||
@ -418,18 +416,18 @@ bool BlitHelper::ConvertDS24S8ToRGBA8(Surface& source, Surface& dest,
|
||||
|
||||
bool BlitHelper::DepthToBuffer(Surface& source, vk::Buffer buffer,
|
||||
const VideoCore::BufferTextureCopy& copy) {
|
||||
std::array<DescriptorData, 3> textures{};
|
||||
textures[0].image_info = vk::DescriptorImageInfo{
|
||||
std::array<vk::DescriptorImageInfo, 3> image_infos;
|
||||
image_infos[0] = vk::DescriptorImageInfo{
|
||||
.sampler = nearest_sampler,
|
||||
.imageView = source.DepthView(),
|
||||
.imageLayout = vk::ImageLayout::eDepthStencilReadOnlyOptimal,
|
||||
};
|
||||
textures[1].image_info = vk::DescriptorImageInfo{
|
||||
image_infos[1] = vk::DescriptorImageInfo{
|
||||
.sampler = nearest_sampler,
|
||||
.imageView = source.StencilView(),
|
||||
.imageLayout = vk::ImageLayout::eDepthStencilReadOnlyOptimal,
|
||||
};
|
||||
textures[2].buffer_info = vk::DescriptorBufferInfo{
|
||||
image_infos[2] = vk::DescriptorBufferInfo{
|
||||
.buffer = buffer,
|
||||
.offset = copy.buffer_offset,
|
||||
.range = copy.buffer_size,
|
||||
@ -437,7 +435,6 @@ bool BlitHelper::DepthToBuffer(Surface& source, vk::Buffer buffer,
|
||||
|
||||
const auto descriptor_set = compute_buffer_provider.Acquire(textures);
|
||||
|
||||
renderpass_cache.EndRendering();
|
||||
scheduler.Record([this, descriptor_set, copy, src_image = source.Image(),
|
||||
extent = source.RealExtent(false)](vk::CommandBuffer cmdbuf) {
|
||||
const vk::ImageMemoryBarrier pre_barrier = {
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
|
||||
#include "video_core/renderer_vulkan/vk_common.h"
|
||||
|
||||
namespace VideoCore {
|
||||
struct TextureBlit;
|
||||
@ -23,8 +23,7 @@ class BlitHelper {
|
||||
friend class TextureRuntime;
|
||||
|
||||
public:
|
||||
BlitHelper(const Instance& instance, Scheduler& scheduler, DescriptorPool& pool,
|
||||
RenderpassCache& renderpass_cache);
|
||||
explicit BlitHelper(const Instance& instance, Scheduler& scheduler);
|
||||
~BlitHelper();
|
||||
|
||||
bool BlitDepthStencil(Surface& source, Surface& dest, const VideoCore::TextureBlit& blit);
|
||||
@ -35,23 +34,16 @@ public:
|
||||
const VideoCore::BufferTextureCopy& copy);
|
||||
|
||||
private:
|
||||
/// Creates compute pipelines used for blit
|
||||
vk::Pipeline MakeComputePipeline(vk::ShaderModule shader, vk::PipelineLayout layout);
|
||||
|
||||
/// Creates graphics pipelines used for blit
|
||||
vk::Pipeline MakeDepthStencilBlitPipeline();
|
||||
|
||||
private:
|
||||
const Instance& instance;
|
||||
Scheduler& scheduler;
|
||||
RenderpassCache& renderpass_cache;
|
||||
|
||||
vk::Device device;
|
||||
vk::RenderPass r32_renderpass;
|
||||
|
||||
DescriptorSetProvider compute_provider;
|
||||
DescriptorSetProvider compute_buffer_provider;
|
||||
DescriptorSetProvider two_textures_provider;
|
||||
vk::PipelineLayout compute_pipeline_layout;
|
||||
vk::PipelineLayout compute_buffer_pipeline_layout;
|
||||
vk::PipelineLayout two_textures_pipeline_layout;
|
||||
|
@ -9,7 +9,6 @@
|
||||
#define VK_NO_PROTOTYPES
|
||||
#define VULKAN_HPP_DISPATCH_LOADER_DYNAMIC 1
|
||||
#define VULKAN_HPP_NO_CONSTRUCTORS
|
||||
#define VULKAN_HPP_NO_UNION_CONSTRUCTORS
|
||||
#define VULKAN_HPP_NO_STRUCT_SETTERS
|
||||
#include <vulkan/vulkan.hpp>
|
||||
|
||||
|
@ -1,141 +0,0 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/microprofile.h"
|
||||
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
|
||||
#include "video_core/renderer_vulkan/vk_instance.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
MICROPROFILE_DEFINE(Vulkan_DescriptorSetAcquire, "Vulkan", "Descriptor Set Acquire",
|
||||
MP_RGB(64, 128, 256));
|
||||
|
||||
constexpr u32 MAX_BATCH_SIZE = 8;
|
||||
|
||||
DescriptorPool::DescriptorPool(const Instance& instance_) : instance{instance_} {
|
||||
auto& pool = pools.emplace_back();
|
||||
pool = CreatePool();
|
||||
}
|
||||
|
||||
DescriptorPool::~DescriptorPool() = default;
|
||||
|
||||
std::vector<vk::DescriptorSet> DescriptorPool::Allocate(vk::DescriptorSetLayout layout,
|
||||
u32 num_sets) {
|
||||
std::array<vk::DescriptorSetLayout, MAX_BATCH_SIZE> layouts;
|
||||
layouts.fill(layout);
|
||||
|
||||
u32 current_pool = 0;
|
||||
vk::DescriptorSetAllocateInfo alloc_info = {
|
||||
.descriptorPool = *pools[current_pool],
|
||||
.descriptorSetCount = num_sets,
|
||||
.pSetLayouts = layouts.data(),
|
||||
};
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
return instance.GetDevice().allocateDescriptorSets(alloc_info);
|
||||
} catch (const vk::OutOfPoolMemoryError&) {
|
||||
current_pool++;
|
||||
if (current_pool == pools.size()) {
|
||||
LOG_INFO(Render_Vulkan, "Run out of pools, creating new one!");
|
||||
auto& pool = pools.emplace_back();
|
||||
pool = CreatePool();
|
||||
}
|
||||
alloc_info.descriptorPool = *pools[current_pool];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vk::DescriptorSet DescriptorPool::Allocate(vk::DescriptorSetLayout layout) {
|
||||
const auto sets = Allocate(layout, 1);
|
||||
return sets[0];
|
||||
}
|
||||
|
||||
vk::UniqueDescriptorPool DescriptorPool::CreatePool() {
|
||||
// Choose a sane pool size good for most games
|
||||
static constexpr std::array<vk::DescriptorPoolSize, 6> pool_sizes = {{
|
||||
{vk::DescriptorType::eUniformBufferDynamic, 64},
|
||||
{vk::DescriptorType::eUniformTexelBuffer, 64},
|
||||
{vk::DescriptorType::eCombinedImageSampler, 4096},
|
||||
{vk::DescriptorType::eSampledImage, 256},
|
||||
{vk::DescriptorType::eStorageImage, 256},
|
||||
{vk::DescriptorType::eStorageBuffer, 32},
|
||||
}};
|
||||
|
||||
const vk::DescriptorPoolCreateInfo descriptor_pool_info = {
|
||||
.maxSets = 4098,
|
||||
.poolSizeCount = static_cast<u32>(pool_sizes.size()),
|
||||
.pPoolSizes = pool_sizes.data(),
|
||||
};
|
||||
|
||||
return instance.GetDevice().createDescriptorPoolUnique(descriptor_pool_info);
|
||||
}
|
||||
|
||||
DescriptorSetProvider::DescriptorSetProvider(
|
||||
const Instance& instance, DescriptorPool& pool_,
|
||||
std::span<const vk::DescriptorSetLayoutBinding> bindings)
|
||||
: pool{pool_}, device{instance.GetDevice()} {
|
||||
std::array<vk::DescriptorUpdateTemplateEntry, MAX_DESCRIPTORS> update_entries;
|
||||
|
||||
for (u32 i = 0; i < bindings.size(); i++) {
|
||||
update_entries[i] = vk::DescriptorUpdateTemplateEntry{
|
||||
.dstBinding = bindings[i].binding,
|
||||
.dstArrayElement = 0,
|
||||
.descriptorCount = bindings[i].descriptorCount,
|
||||
.descriptorType = bindings[i].descriptorType,
|
||||
.offset = i * sizeof(DescriptorData),
|
||||
.stride = sizeof(DescriptorData),
|
||||
};
|
||||
}
|
||||
|
||||
const vk::DescriptorSetLayoutCreateInfo layout_info = {
|
||||
.bindingCount = static_cast<u32>(bindings.size()),
|
||||
.pBindings = bindings.data(),
|
||||
};
|
||||
layout = device.createDescriptorSetLayoutUnique(layout_info);
|
||||
|
||||
const vk::DescriptorUpdateTemplateCreateInfo template_info = {
|
||||
.descriptorUpdateEntryCount = static_cast<u32>(bindings.size()),
|
||||
.pDescriptorUpdateEntries = update_entries.data(),
|
||||
.templateType = vk::DescriptorUpdateTemplateType::eDescriptorSet,
|
||||
.descriptorSetLayout = *layout,
|
||||
};
|
||||
update_template = device.createDescriptorUpdateTemplateUnique(template_info);
|
||||
}
|
||||
|
||||
DescriptorSetProvider::~DescriptorSetProvider() = default;
|
||||
|
||||
vk::DescriptorSet DescriptorSetProvider::Acquire(std::span<const DescriptorData> data) {
|
||||
MICROPROFILE_SCOPE(Vulkan_DescriptorSetAcquire);
|
||||
DescriptorSetData key{};
|
||||
std::memcpy(key.data(), data.data(), data.size_bytes());
|
||||
const auto [it, new_set] = descriptor_set_map.try_emplace(key);
|
||||
if (!new_set) {
|
||||
return it->second;
|
||||
}
|
||||
if (free_sets.empty()) {
|
||||
free_sets = pool.Allocate(*layout, MAX_BATCH_SIZE);
|
||||
}
|
||||
it.value() = free_sets.back();
|
||||
free_sets.pop_back();
|
||||
device.updateDescriptorSetWithTemplate(it->second, *update_template, data[0]);
|
||||
return it->second;
|
||||
}
|
||||
|
||||
void DescriptorSetProvider::FreeWithImage(vk::ImageView image_view) {
|
||||
for (auto it = descriptor_set_map.begin(); it != descriptor_set_map.end();) {
|
||||
const auto& [data, set] = *it;
|
||||
const bool has_image = std::any_of(data.begin(), data.end(), [image_view](auto& info) {
|
||||
return info.image_info.imageView == image_view;
|
||||
});
|
||||
if (has_image) {
|
||||
free_sets.push_back(set);
|
||||
it = descriptor_set_map.erase(it);
|
||||
} else {
|
||||
it++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
@ -1,92 +0,0 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <span>
|
||||
#include <vector>
|
||||
#include <tsl/robin_map.h>
|
||||
|
||||
#include "common/hash.h"
|
||||
#include "video_core/renderer_vulkan/vk_common.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
class Instance;
|
||||
|
||||
constexpr u32 MAX_DESCRIPTORS = 7;
|
||||
|
||||
union DescriptorData {
|
||||
vk::DescriptorImageInfo image_info;
|
||||
vk::DescriptorBufferInfo buffer_info;
|
||||
vk::BufferView buffer_view;
|
||||
|
||||
bool operator==(const DescriptorData& other) const noexcept {
|
||||
return std::memcmp(this, &other, sizeof(DescriptorData)) == 0;
|
||||
}
|
||||
};
|
||||
|
||||
using DescriptorSetData = std::array<DescriptorData, MAX_DESCRIPTORS>;
|
||||
|
||||
struct DataHasher {
|
||||
u64 operator()(const DescriptorSetData& data) const noexcept {
|
||||
return Common::ComputeHash64(data.data(), sizeof(data));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* An interface for allocating descriptor sets that manages a collection of descriptor pools.
|
||||
*/
|
||||
class DescriptorPool {
|
||||
public:
|
||||
explicit DescriptorPool(const Instance& instance);
|
||||
~DescriptorPool();
|
||||
|
||||
std::vector<vk::DescriptorSet> Allocate(vk::DescriptorSetLayout layout, u32 num_sets);
|
||||
|
||||
vk::DescriptorSet Allocate(vk::DescriptorSetLayout layout);
|
||||
|
||||
private:
|
||||
vk::UniqueDescriptorPool CreatePool();
|
||||
|
||||
private:
|
||||
const Instance& instance;
|
||||
std::vector<vk::UniqueDescriptorPool> pools;
|
||||
};
|
||||
|
||||
/**
|
||||
* Allocates and caches descriptor sets of a specific layout.
|
||||
*/
|
||||
class DescriptorSetProvider {
|
||||
public:
|
||||
explicit DescriptorSetProvider(const Instance& instance, DescriptorPool& pool,
|
||||
std::span<const vk::DescriptorSetLayoutBinding> bindings);
|
||||
~DescriptorSetProvider();
|
||||
|
||||
vk::DescriptorSet Acquire(std::span<const DescriptorData> data);
|
||||
|
||||
void FreeWithImage(vk::ImageView image_view);
|
||||
|
||||
[[nodiscard]] vk::DescriptorSetLayout Layout() const noexcept {
|
||||
return *layout;
|
||||
}
|
||||
|
||||
[[nodiscard]] vk::DescriptorSetLayout& Layout() noexcept {
|
||||
return layout.get();
|
||||
}
|
||||
|
||||
[[nodiscard]] vk::DescriptorUpdateTemplate UpdateTemplate() const noexcept {
|
||||
return *update_template;
|
||||
}
|
||||
|
||||
private:
|
||||
DescriptorPool& pool;
|
||||
vk::Device device;
|
||||
vk::UniqueDescriptorSetLayout layout;
|
||||
vk::UniqueDescriptorUpdateTemplate update_template;
|
||||
std::vector<vk::DescriptorSet> free_sets;
|
||||
tsl::robin_map<DescriptorSetData, vk::DescriptorSet, DataHasher> descriptor_set_map;
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
75
src/video_core/renderer_vulkan/vk_descriptor_update.cpp
Normal file
75
src/video_core/renderer_vulkan/vk_descriptor_update.cpp
Normal file
@ -0,0 +1,75 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "video_core/renderer_vulkan/vk_descriptor_update.h"
|
||||
#include "video_core/renderer_vulkan/vk_instance.h"
|
||||
#include "video_core/renderer_vulkan/vk_scheduler.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
constexpr size_t NUM_MAX_DESCRIPTORS = 5;
|
||||
|
||||
DescriptorSetSpec::DescriptorSetSpec(const Instance& instance,
|
||||
std::span<const vk::DescriptorSetLayoutBinding> bindings) {
|
||||
const vk::Device device = instance.GetDevice();
|
||||
std::array<vk::DescriptorUpdateTemplateEntry, NUM_MAX_DESCRIPTORS> update_entries;
|
||||
|
||||
for (u32 i = 0; i < bindings.size(); i++) {
|
||||
update_entries[i] = vk::DescriptorUpdateTemplateEntry{
|
||||
.dstBinding = bindings[i].binding,
|
||||
.dstArrayElement = 0,
|
||||
.descriptorCount = bindings[i].descriptorCount,
|
||||
.descriptorType = bindings[i].descriptorType,
|
||||
.offset = i * sizeof(DescriptorUpdateEntry),
|
||||
.stride = sizeof(DescriptorUpdateEntry),
|
||||
};
|
||||
}
|
||||
|
||||
const vk::DescriptorSetLayoutCreateInfo layout_info = {
|
||||
.bindingCount = static_cast<u32>(bindings.size()),
|
||||
.pBindings = bindings.data(),
|
||||
};
|
||||
descriptor_set_layout = device.createDescriptorSetLayoutUnique(layout_info);
|
||||
|
||||
const vk::DescriptorUpdateTemplateCreateInfo template_info = {
|
||||
.descriptorUpdateEntryCount = static_cast<u32>(bindings.size()),
|
||||
.pDescriptorUpdateEntries = update_entries.data(),
|
||||
.templateType = vk::DescriptorUpdateTemplateType::eDescriptorSet,
|
||||
.descriptorSetLayout = descriptor_set_layout.get(),
|
||||
};
|
||||
update_template = device.createDescriptorUpdateTemplateUnique(template_info);
|
||||
}
|
||||
|
||||
DescriptorSetSpec::~DescriptorSetSpec() = default;
|
||||
|
||||
DescriptorUpdateQueue::DescriptorUpdateQueue(Scheduler& scheduler_, size_t num_frames_)
|
||||
: scheduler{scheduler_}, num_frames{num_frames_} {
|
||||
frame_payload_size = PAYLOAD_SIZE / num_frames;
|
||||
payload_start = payload.data();
|
||||
payload_cursor = payload.data();
|
||||
}
|
||||
|
||||
DescriptorUpdateQueue::~DescriptorUpdateQueue() = default;
|
||||
|
||||
void DescriptorUpdateQueue::TickFrame() {
|
||||
if (++frame_index >= num_frames) {
|
||||
frame_index = 0;
|
||||
}
|
||||
payload_start = payload.data() + frame_index * frame_payload_size;
|
||||
payload_cursor = payload_start;
|
||||
}
|
||||
|
||||
void DescriptorUpdateQueue::Acquire() {
|
||||
// This is the maximum number of entries a single draw call might use.
|
||||
static constexpr size_t MAX_ENTRIES = 0x14;
|
||||
|
||||
if (std::distance(payload_start, payload_cursor) + MAX_ENTRIES >= frame_payload_size) {
|
||||
LOG_WARNING(Render_Vulkan, "Payload overflow, waiting for worker thread");
|
||||
scheduler.WaitWorker();
|
||||
payload_cursor = payload_start;
|
||||
}
|
||||
upload_start = payload_cursor;
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
96
src/video_core/renderer_vulkan/vk_descriptor_update.h
Normal file
96
src/video_core/renderer_vulkan/vk_descriptor_update.h
Normal file
@ -0,0 +1,96 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/literals.h"
|
||||
#include "video_core/renderer_vulkan/vk_common.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
class Instance;
|
||||
class Scheduler;
|
||||
|
||||
union DescriptorUpdateEntry {
|
||||
vk::DescriptorImageInfo image;
|
||||
vk::DescriptorBufferInfo buffer;
|
||||
vk::BufferView texel_buffer;
|
||||
};
|
||||
|
||||
class DescriptorSetSpec {
|
||||
public:
|
||||
explicit DescriptorSetSpec(const Instance& instance,
|
||||
std::span<const vk::DescriptorSetLayoutBinding> bindings);
|
||||
~DescriptorSetSpec();
|
||||
|
||||
vk::DescriptorSetLayout Layout() const noexcept {
|
||||
return descriptor_set_layout.get();
|
||||
}
|
||||
|
||||
vk::DescriptorUpdateTemplate Template() const noexcept {
|
||||
return update_template.get();
|
||||
}
|
||||
|
||||
private:
|
||||
vk::UniqueDescriptorSetLayout descriptor_set_layout;
|
||||
vk::UniqueDescriptorUpdateTemplate update_template;
|
||||
};
|
||||
|
||||
using namespace Common::Literals;
|
||||
|
||||
class DescriptorUpdateQueue final {
|
||||
static constexpr size_t PAYLOAD_SIZE = 1_MiB;
|
||||
|
||||
public:
|
||||
explicit DescriptorUpdateQueue(Scheduler& scheduler, size_t num_frames);
|
||||
~DescriptorUpdateQueue();
|
||||
|
||||
void TickFrame();
|
||||
|
||||
void Acquire();
|
||||
|
||||
const DescriptorUpdateEntry* UpdateData() const noexcept {
|
||||
return upload_start;
|
||||
}
|
||||
|
||||
void AddImage(vk::ImageView image_view) {
|
||||
(payload_cursor++)->image = vk::DescriptorImageInfo{
|
||||
.sampler = VK_NULL_HANDLE,
|
||||
.imageView = image_view,
|
||||
.imageLayout = vk::ImageLayout::eGeneral,
|
||||
};
|
||||
}
|
||||
|
||||
void AddSampledImage(vk::ImageView image_view, vk::Sampler sampler) {
|
||||
(payload_cursor++)->image = vk::DescriptorImageInfo{
|
||||
.sampler = sampler,
|
||||
.imageView = image_view,
|
||||
.imageLayout = vk::ImageLayout::eGeneral,
|
||||
};
|
||||
}
|
||||
|
||||
void AddBuffer(vk::Buffer buffer, vk::DeviceSize offset, vk::DeviceSize size) {
|
||||
(payload_cursor++)->buffer = vk::DescriptorBufferInfo{
|
||||
.buffer = buffer,
|
||||
.offset = offset,
|
||||
.range = size,
|
||||
};
|
||||
}
|
||||
|
||||
void AddTexelBuffer(vk::BufferView texel_buffer) {
|
||||
*(payload_cursor++)->texel_buffer = texel_buffer;
|
||||
}
|
||||
|
||||
private:
|
||||
Scheduler& scheduler;
|
||||
size_t num_frames{};
|
||||
size_t frame_payload_size{};
|
||||
size_t frame_index{};
|
||||
DescriptorUpdateEntry* payload_cursor{};
|
||||
DescriptorUpdateEntry* payload_start{};
|
||||
const DescriptorUpdateEntry* upload_start{};
|
||||
std::array<DescriptorUpdateEntry, PAYLOAD_SIZE> payload;
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
@ -272,10 +272,10 @@ bool GraphicsPipeline::Build(bool fail_on_compile_required) {
|
||||
pipeline_info.flags |= vk::PipelineCreateFlagBits::eFailOnPipelineCompileRequiredEXT;
|
||||
}
|
||||
|
||||
auto result = device.createGraphicsPipelineUnique(pipeline_cache, pipeline_info);
|
||||
if (result.result == vk::Result::eSuccess) {
|
||||
pipeline = std::move(result.value);
|
||||
} else if (result.result == vk::Result::eErrorPipelineCompileRequiredEXT) {
|
||||
auto [result, handle] = device.createGraphicsPipelineUnique(pipeline_cache, pipeline_info);
|
||||
if (result == vk::Result::eSuccess) {
|
||||
pipeline = std::move(handle);
|
||||
} else if (result == vk::Result::eErrorPipelineCompileRequiredEXT) {
|
||||
return false;
|
||||
} else {
|
||||
UNREACHABLE_MSG("Graphics pipeline creation failed!");
|
||||
|
@ -42,22 +42,18 @@ namespace Vulkan {
|
||||
class Instance;
|
||||
class RenderpassCache;
|
||||
|
||||
constexpr u32 MAX_SHADER_STAGES = 3;
|
||||
constexpr u32 MAX_VERTEX_ATTRIBUTES = 16;
|
||||
constexpr u32 MAX_VERTEX_BINDINGS = 13;
|
||||
constexpr size_t MAX_SHADER_STAGES = 3;
|
||||
constexpr size_t MAX_VERTEX_ATTRIBUTES = 16;
|
||||
constexpr size_t MAX_VERTEX_BINDINGS = 13;
|
||||
|
||||
/**
|
||||
* The pipeline state is tightly packed with bitfields to reduce
|
||||
* the overhead of hashing as much as possible
|
||||
*/
|
||||
union RasterizationState {
|
||||
u8 value = 0;
|
||||
u32 raw;
|
||||
BitField<0, 2, Pica::PipelineRegs::TriangleTopology> topology;
|
||||
BitField<4, 2, Pica::RasterizerRegs::CullMode> cull_mode;
|
||||
};
|
||||
|
||||
union DepthStencilState {
|
||||
u32 value = 0;
|
||||
u32 raw;
|
||||
BitField<0, 1, u32> depth_test_enable;
|
||||
BitField<1, 1, u32> depth_write_enable;
|
||||
BitField<2, 1, u32> stencil_test_enable;
|
||||
@ -73,7 +69,7 @@ struct BlendingState {
|
||||
u16 color_write_mask;
|
||||
Pica::FramebufferRegs::LogicOp logic_op;
|
||||
union {
|
||||
u32 value = 0;
|
||||
u32 raw;
|
||||
BitField<0, 4, Pica::FramebufferRegs::BlendFactor> src_color_blend_factor;
|
||||
BitField<4, 4, Pica::FramebufferRegs::BlendFactor> dst_color_blend_factor;
|
||||
BitField<8, 3, Pica::FramebufferRegs::BlendEquation> color_blend_eq;
|
||||
@ -84,10 +80,11 @@ struct BlendingState {
|
||||
};
|
||||
|
||||
struct DynamicState {
|
||||
u32 blend_color = 0;
|
||||
u32 blend_color;
|
||||
u8 stencil_reference;
|
||||
u8 stencil_compare_mask;
|
||||
u8 stencil_write_mask;
|
||||
INSERT_PADDING_BYTES(1);
|
||||
|
||||
Common::Rectangle<u32> scissor;
|
||||
Common::Rectangle<s32> viewport;
|
||||
@ -129,12 +126,19 @@ struct AttachmentInfo {
|
||||
* Information about a graphics/compute pipeline
|
||||
*/
|
||||
struct PipelineInfo {
|
||||
BlendingState blending;
|
||||
AttachmentInfo attachments;
|
||||
RasterizationState rasterization;
|
||||
DepthStencilState depth_stencil;
|
||||
DynamicState dynamic;
|
||||
VertexLayout vertex_layout;
|
||||
BlendingState blending{};
|
||||
AttachmentInfo attachments{};
|
||||
RasterizationState rasterization{};
|
||||
DepthStencilState depth_stencil{};
|
||||
DynamicState dynamic{};
|
||||
VertexLayout vertex_layout{};
|
||||
|
||||
enum Type : u32 {
|
||||
Normal,
|
||||
ShadowPlane,
|
||||
ShadowCube,
|
||||
};
|
||||
Type type{Type::Normal};
|
||||
|
||||
[[nodiscard]] u64 Hash(const Instance& instance) const;
|
||||
|
||||
@ -148,6 +152,7 @@ struct PipelineInfo {
|
||||
return depth_write || stencil_write;
|
||||
}
|
||||
};
|
||||
static_assert(std::has_unique_object_representations_v<PipelineInfo>);
|
||||
|
||||
struct Shader : public Common::AsyncHandle {
|
||||
explicit Shader(const Instance& instance);
|
||||
@ -176,7 +181,7 @@ public:
|
||||
bool Build(bool fail_on_compile_required = false);
|
||||
|
||||
[[nodiscard]] vk::Pipeline Handle() const noexcept {
|
||||
return *pipeline;
|
||||
return pipeline.get();
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "common/settings.h"
|
||||
#include "video_core/renderer_vulkan/pica_to_vk.h"
|
||||
#include "video_core/renderer_vulkan/vk_instance.h"
|
||||
@ -26,6 +27,12 @@ MICROPROFILE_DEFINE(Vulkan_Bind, "Vulkan", "Pipeline Bind", MP_RGB(192, 32, 32))
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
enum DescriptorSet {
|
||||
Buffer,
|
||||
Texture,
|
||||
Utility,
|
||||
};
|
||||
|
||||
u32 AttribBytes(Pica::PipelineRegs::VertexAttributeFormat format, u32 size) {
|
||||
switch (format) {
|
||||
case Pica::PipelineRegs::VertexAttributeFormat::FLOAT:
|
||||
@ -61,35 +68,33 @@ constexpr std::array<vk::DescriptorSetLayoutBinding, 6> BUFFER_BINDINGS = {{
|
||||
{5, vk::DescriptorType::eUniformTexelBuffer, 1, vk::ShaderStageFlagBits::eFragment},
|
||||
}};
|
||||
|
||||
constexpr std::array<vk::DescriptorSetLayoutBinding, 4> TEXTURE_BINDINGS = {{
|
||||
{0, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment},
|
||||
template <vk::DescriptorType tex0_type, u32 num_faces>
|
||||
constexpr std::array<vk::DescriptorSetLayoutBinding, 3> TEXTURE_BINDINGS = {{
|
||||
{0, tex0_type, num_faces, vk::ShaderStageFlagBits::eFragment},
|
||||
{1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment},
|
||||
{2, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment},
|
||||
{3, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment},
|
||||
}};
|
||||
|
||||
// TODO: Use descriptor array for shadow cube
|
||||
constexpr std::array<vk::DescriptorSetLayoutBinding, 7> SHADOW_BINDINGS = {{
|
||||
constexpr std::array<vk::DescriptorSetLayoutBinding, 1> UTILITY_BINDINGS = {{
|
||||
{0, vk::DescriptorType::eStorageImage, 1, vk::ShaderStageFlagBits::eFragment},
|
||||
{1, vk::DescriptorType::eStorageImage, 1, vk::ShaderStageFlagBits::eFragment},
|
||||
{2, vk::DescriptorType::eStorageImage, 1, vk::ShaderStageFlagBits::eFragment},
|
||||
{3, vk::DescriptorType::eStorageImage, 1, vk::ShaderStageFlagBits::eFragment},
|
||||
{4, vk::DescriptorType::eStorageImage, 1, vk::ShaderStageFlagBits::eFragment},
|
||||
{5, vk::DescriptorType::eStorageImage, 1, vk::ShaderStageFlagBits::eFragment},
|
||||
{6, vk::DescriptorType::eStorageImage, 1, vk::ShaderStageFlagBits::eFragment},
|
||||
}};
|
||||
|
||||
PipelineCache::PipelineCache(const Instance& instance_, Scheduler& scheduler_,
|
||||
RenderpassCache& renderpass_cache_, DescriptorPool& pool_)
|
||||
: instance{instance_}, scheduler{scheduler_}, renderpass_cache{renderpass_cache_}, pool{pool_},
|
||||
RenderpassCache& renderpass_cache_, DescriptorPool& persistent_pool)
|
||||
: instance{instance_}, scheduler{scheduler_},
|
||||
renderpass_cache{renderpass_cache_}, pool{instance, scheduler.GetMasterSemaphore()},
|
||||
num_worker_threads{std::max(std::thread::hardware_concurrency(), 2U)},
|
||||
workers{num_worker_threads, "Pipeline workers"},
|
||||
descriptor_set_providers{DescriptorSetProvider{instance, pool, BUFFER_BINDINGS},
|
||||
DescriptorSetProvider{instance, pool, TEXTURE_BINDINGS},
|
||||
DescriptorSetProvider{instance, pool, SHADOW_BINDINGS}},
|
||||
workers{num_worker_threads, "Pipeline workers"}, buffer_set_spec{instance, BUFFER_BINDINGS},
|
||||
utility_set_spec{instance, UTILITY_BINDINGS},
|
||||
texture_set_specs{
|
||||
DescriptorSetSpec{instance,
|
||||
TEXTURE_BINDINGS<vk::DescriptorType::eCombinedImageSampler, 1>},
|
||||
DescriptorSetSpec{instance, TEXTURE_BINDINGS<vk::DescriptorType::eStorageImage, 1>},
|
||||
DescriptorSetSpec{instance, TEXTURE_BINDINGS<vk::DescriptorType::eStorageImage, 6>}},
|
||||
trivial_vertex_shader{
|
||||
instance, vk::ShaderStageFlagBits::eVertex,
|
||||
GLSL::GenerateTrivialVertexShader(instance.IsShaderClipDistanceSupported(), true)} {
|
||||
// Create profile for driver assisted shader features.
|
||||
profile = Pica::Shader::Profile{
|
||||
.has_separable_shaders = true,
|
||||
.has_clip_planes = instance.IsShaderClipDistanceSupported(),
|
||||
@ -102,14 +107,10 @@ PipelineCache::PipelineCache(const Instance& instance_, Scheduler& scheduler_,
|
||||
.has_logic_op = !instance.NeedsLogicOpEmulation(),
|
||||
.is_vulkan = true,
|
||||
};
|
||||
BuildLayout();
|
||||
}
|
||||
|
||||
void PipelineCache::BuildLayout() {
|
||||
std::array<vk::DescriptorSetLayout, NUM_RASTERIZER_SETS> descriptor_set_layouts;
|
||||
std::transform(descriptor_set_providers.begin(), descriptor_set_providers.end(),
|
||||
descriptor_set_layouts.begin(),
|
||||
[](const auto& provider) { return provider.Layout(); });
|
||||
descriptor_set_layouts[DescriptorSet::Buffer] = buffer_set_spec.Layout();
|
||||
descriptor_set_layouts[DescriptorSet::Utility] = utility_set_spec.Layout();
|
||||
|
||||
const vk::PipelineLayoutCreateInfo layout_info = {
|
||||
.setLayoutCount = NUM_RASTERIZER_SETS,
|
||||
@ -117,7 +118,15 @@ void PipelineCache::BuildLayout() {
|
||||
.pushConstantRangeCount = 0,
|
||||
.pPushConstantRanges = nullptr,
|
||||
};
|
||||
pipeline_layout = instance.GetDevice().createPipelineLayoutUnique(layout_info);
|
||||
|
||||
// Create rasterizer pipeline layouts.
|
||||
for (size_t i = 0; i < NUM_PIPELINE_CONFIGS; i++) {
|
||||
descriptor_set_layouts[DescriptorSet::Texture] = texture_set_specs[i].Layout();
|
||||
pipeline_layouts[i] = instance.GetDevice().createPipelineLayoutUnique(layout_info);
|
||||
}
|
||||
|
||||
// Allocate buffer descriptor set from the persistent pool
|
||||
bound_descriptor_sets[DescriptorSet::Buffer] = persistent_pool.Commit(buffer_set_spec.Layout());
|
||||
}
|
||||
|
||||
PipelineCache::~PipelineCache() {
|
||||
@ -129,34 +138,40 @@ void PipelineCache::LoadDiskCache() {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string cache_file_path = fmt::format("{}{:x}{:x}.bin", GetPipelineCacheDir(),
|
||||
instance.GetVendorID(), instance.GetDeviceID());
|
||||
vk::PipelineCacheCreateInfo cache_info = {
|
||||
.initialDataSize = 0,
|
||||
.pInitialData = nullptr,
|
||||
};
|
||||
const auto cache_dir = GetPipelineCacheDir();
|
||||
const u32 vendor_id = instance.GetVendorID();
|
||||
const u32 device_id = instance.GetDeviceID();
|
||||
const auto cache_file_path = fmt::format("{}{:x}{:x}.bin", cache_dir, vendor_id, device_id);
|
||||
|
||||
vk::PipelineCacheCreateInfo cache_info{};
|
||||
std::vector<u8> cache_data;
|
||||
FileUtil::IOFile cache_file{cache_file_path, "r"};
|
||||
if (cache_file.IsOpen()) {
|
||||
LOG_INFO(Render_Vulkan, "Loading pipeline cache");
|
||||
|
||||
const u64 cache_file_size = cache_file.GetSize();
|
||||
cache_data.resize(cache_file_size);
|
||||
if (cache_file.ReadBytes(cache_data.data(), cache_file_size)) {
|
||||
if (!IsCacheValid(cache_data)) {
|
||||
LOG_WARNING(Render_Vulkan, "Pipeline cache provided invalid, ignoring");
|
||||
} else {
|
||||
cache_info.initialDataSize = cache_file_size;
|
||||
cache_info.pInitialData = cache_data.data();
|
||||
}
|
||||
}
|
||||
SCOPE_EXIT({
|
||||
const vk::Device device = instance.GetDevice();
|
||||
pipeline_cache = device.createPipelineCacheUnique(cache_info);
|
||||
});
|
||||
|
||||
cache_file.Close();
|
||||
FileUtil::IOFile cache_file{cache_file_path, "rb"};
|
||||
if (!cache_file.IsOpen()) {
|
||||
LOG_INFO(Render_Vulkan, "No pipeline cache found for device");
|
||||
return;
|
||||
}
|
||||
|
||||
vk::Device device = instance.GetDevice();
|
||||
pipeline_cache = device.createPipelineCacheUnique(cache_info);
|
||||
const u64 cache_file_size = cache_file.GetSize();
|
||||
cache_data.resize(cache_file_size);
|
||||
if (cache_file.ReadBytes(cache_data.data(), cache_file_size) != cache_file_size) {
|
||||
LOG_ERROR(Render_Vulkan, "Error during pipeline cache read, removing");
|
||||
FileUtil::Delete(cache_file_path);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsCacheValid(cache_data)) {
|
||||
LOG_WARNING(Render_Vulkan, "Pipeline cache provided invalid, ignoring");
|
||||
}
|
||||
|
||||
LOG_INFO(Render_Vulkan, "Loading pipeline cache with size {} KB", cache_file_size / 1024);
|
||||
cache_info.initialDataSize = cache_file_size;
|
||||
cache_info.pInitialData = cache_data.data();
|
||||
}
|
||||
|
||||
void PipelineCache::SaveDiskCache() {
|
||||
@ -164,22 +179,23 @@ void PipelineCache::SaveDiskCache() {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string cache_file_path = fmt::format("{}{:x}{:x}.bin", GetPipelineCacheDir(),
|
||||
instance.GetVendorID(), instance.GetDeviceID());
|
||||
const auto cache_dir = GetPipelineCacheDir();
|
||||
const u32 vendor_id = instance.GetVendorID();
|
||||
const u32 device_id = instance.GetDeviceID();
|
||||
const auto cache_file_path = fmt::format("{}{:x}{:x}.bin", cache_dir, vendor_id, device_id);
|
||||
|
||||
FileUtil::IOFile cache_file{cache_file_path, "wb"};
|
||||
if (!cache_file.IsOpen()) {
|
||||
LOG_ERROR(Render_Vulkan, "Unable to open pipeline cache for writing");
|
||||
return;
|
||||
}
|
||||
|
||||
vk::Device device = instance.GetDevice();
|
||||
auto cache_data = device.getPipelineCacheData(*pipeline_cache);
|
||||
if (!cache_file.WriteBytes(cache_data.data(), cache_data.size())) {
|
||||
LOG_ERROR(Render_Vulkan, "Error during pipeline cache write");
|
||||
return;
|
||||
const vk::Device device = instance.GetDevice();
|
||||
const auto cache_data = device.getPipelineCacheData(*pipeline_cache);
|
||||
if (cache_file.WriteBytes(cache_data.data(), cache_data.size()) != cache_data.size()) {
|
||||
LOG_ERROR(Render_Vulkan, "Error during pipeline cache write, removing");
|
||||
FileUtil::Delete(cache_file_path);
|
||||
}
|
||||
|
||||
cache_file.Close();
|
||||
}
|
||||
|
||||
bool PipelineCache::BindPipeline(const PipelineInfo& info, bool wait_built) {
|
||||
@ -195,12 +211,14 @@ bool PipelineCache::BindPipeline(const PipelineInfo& info, bool wait_built) {
|
||||
|
||||
auto [it, new_pipeline] = graphics_pipelines.try_emplace(pipeline_hash);
|
||||
if (new_pipeline) {
|
||||
it.value() =
|
||||
std::make_unique<GraphicsPipeline>(instance, renderpass_cache, info, *pipeline_cache,
|
||||
*pipeline_layout, current_shaders, &workers);
|
||||
const auto pipeline_layout = pipeline_layouts[info.type].get();
|
||||
auto pipeline = std::make_unique<GraphicsPipeline>(instance, renderpass_cache, info,
|
||||
pipeline_cache.get(), pipeline_layout,
|
||||
current_shaders, &workers);
|
||||
it.value() = std::move(pipeline);
|
||||
}
|
||||
|
||||
GraphicsPipeline* const pipeline{it->second.get()};
|
||||
GraphicsPipeline* const pipeline = it->second.get();
|
||||
if (!pipeline->IsDone() && !pipeline->TryBuild(wait_built)) {
|
||||
return false;
|
||||
}
|
||||
@ -403,7 +421,7 @@ bool PipelineCache::UseProgrammableVertexShader(const Pica::Regs& regs,
|
||||
}
|
||||
|
||||
auto [iter, new_program] = programmable_vertex_cache.try_emplace(program, instance);
|
||||
auto& shader = iter->second;
|
||||
auto& shader = iter.value();
|
||||
|
||||
if (new_program) {
|
||||
shader.program = std::move(program);
|
||||
@ -414,7 +432,7 @@ bool PipelineCache::UseProgrammableVertexShader(const Pica::Regs& regs,
|
||||
});
|
||||
}
|
||||
|
||||
it->second = &shader;
|
||||
it.value() = &shader;
|
||||
}
|
||||
|
||||
Shader* const shader{it->second};
|
||||
@ -442,7 +460,7 @@ bool PipelineCache::UseFixedGeometryShader(const Pica::Regs& regs) {
|
||||
|
||||
const PicaFixedGSConfig gs_config{regs, instance.IsShaderClipDistanceSupported()};
|
||||
auto [it, new_shader] = fixed_geometry_shaders.try_emplace(gs_config, instance);
|
||||
auto& shader = it->second;
|
||||
auto& shader = it.value();
|
||||
|
||||
if (new_shader) {
|
||||
workers.QueueWork([gs_config, device = instance.GetDevice(), &shader]() {
|
||||
@ -467,7 +485,7 @@ void PipelineCache::UseFragmentShader(const Pica::Regs& regs,
|
||||
const Pica::Shader::UserConfig& user) {
|
||||
const FSConfig fs_config{regs, user, profile};
|
||||
const auto [it, new_shader] = fragment_shaders.try_emplace(fs_config, instance);
|
||||
auto& shader = it->second;
|
||||
auto& shader = it.value();
|
||||
|
||||
if (new_shader) {
|
||||
const bool use_spirv = Settings::values.spirv_shader_gen.GetValue();
|
||||
@ -489,52 +507,6 @@ void PipelineCache::UseFragmentShader(const Pica::Regs& regs,
|
||||
shader_hashes[ProgramType::FS] = fs_config.Hash();
|
||||
}
|
||||
|
||||
void PipelineCache::BindTexture(u32 binding, vk::ImageView image_view, vk::Sampler sampler) {
|
||||
auto& info = update_data[1][binding].image_info;
|
||||
if (info.imageView == image_view && info.sampler == sampler) {
|
||||
return;
|
||||
}
|
||||
set_dirty[1] = true;
|
||||
info = vk::DescriptorImageInfo{
|
||||
.sampler = sampler,
|
||||
.imageView = image_view,
|
||||
.imageLayout = vk::ImageLayout::eGeneral,
|
||||
};
|
||||
}
|
||||
|
||||
void PipelineCache::BindStorageImage(u32 binding, vk::ImageView image_view) {
|
||||
auto& info = update_data[2][binding].image_info;
|
||||
if (info.imageView == image_view) {
|
||||
return;
|
||||
}
|
||||
set_dirty[2] = true;
|
||||
info = vk::DescriptorImageInfo{
|
||||
.imageView = image_view,
|
||||
.imageLayout = vk::ImageLayout::eGeneral,
|
||||
};
|
||||
}
|
||||
|
||||
void PipelineCache::BindBuffer(u32 binding, vk::Buffer buffer, u32 offset, u32 size) {
|
||||
auto& info = update_data[0][binding].buffer_info;
|
||||
if (info.buffer == buffer && info.offset == offset && info.range == size) {
|
||||
return;
|
||||
}
|
||||
set_dirty[0] = true;
|
||||
info = vk::DescriptorBufferInfo{
|
||||
.buffer = buffer,
|
||||
.offset = offset,
|
||||
.range = size,
|
||||
};
|
||||
}
|
||||
|
||||
void PipelineCache::BindTexelBuffer(u32 binding, vk::BufferView buffer_view) {
|
||||
auto& view = update_data[0][binding].buffer_view;
|
||||
if (view != buffer_view) {
|
||||
set_dirty[0] = true;
|
||||
view = buffer_view;
|
||||
}
|
||||
}
|
||||
|
||||
void PipelineCache::SetBufferOffset(u32 binding, size_t offset) {
|
||||
if (offsets[binding] != static_cast<u32>(offset)) {
|
||||
offsets[binding] = static_cast<u32>(offset);
|
||||
|
@ -6,9 +6,11 @@
|
||||
|
||||
#include <bitset>
|
||||
#include <tsl/robin_map.h>
|
||||
#include "common/thread_worker.h"
|
||||
|
||||
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
|
||||
#include "video_core/renderer_vulkan/vk_descriptor_update.h"
|
||||
#include "video_core/renderer_vulkan/vk_graphics_pipeline.h"
|
||||
#include "video_core/renderer_vulkan/vk_resource_pool.h"
|
||||
#include "video_core/shader/generator/pica_fs_config.h"
|
||||
#include "video_core/shader/generator/profile.h"
|
||||
#include "video_core/shader/generator/shader_gen.h"
|
||||
@ -28,22 +30,25 @@ class Scheduler;
|
||||
class RenderpassCache;
|
||||
class DescriptorPool;
|
||||
|
||||
constexpr u32 NUM_RASTERIZER_SETS = 3;
|
||||
constexpr u32 NUM_DYNAMIC_OFFSETS = 3;
|
||||
enum class PipelineConfig {
|
||||
Normal,
|
||||
ShadowPlane,
|
||||
ShadowCube,
|
||||
};
|
||||
|
||||
/**
|
||||
* Stores a collection of rasterizer pipelines used during rendering.
|
||||
*/
|
||||
class PipelineCache {
|
||||
static constexpr size_t NUM_RASTERIZER_SETS = 3;
|
||||
static constexpr size_t NUM_PIPELINE_CONFIGS = 3;
|
||||
static constexpr size_t NUM_DYNAMIC_OFFSETS = 3;
|
||||
|
||||
public:
|
||||
explicit PipelineCache(const Instance& instance, Scheduler& scheduler,
|
||||
RenderpassCache& renderpass_cache, DescriptorPool& pool);
|
||||
~PipelineCache();
|
||||
|
||||
[[nodiscard]] DescriptorSetProvider& TextureProvider() noexcept {
|
||||
return descriptor_set_providers[1];
|
||||
}
|
||||
|
||||
/// Loads the pipeline cache stored to disk
|
||||
void LoadDiskCache();
|
||||
|
||||
@ -69,25 +74,10 @@ public:
|
||||
/// Binds a fragment shader generated from PICA state
|
||||
void UseFragmentShader(const Pica::Regs& regs, const Pica::Shader::UserConfig& user);
|
||||
|
||||
/// Binds a texture to the specified binding
|
||||
void BindTexture(u32 binding, vk::ImageView image_view, vk::Sampler sampler);
|
||||
|
||||
/// Binds a storage image to the specified binding
|
||||
void BindStorageImage(u32 binding, vk::ImageView image_view);
|
||||
|
||||
/// Binds a buffer to the specified binding
|
||||
void BindBuffer(u32 binding, vk::Buffer buffer, u32 offset, u32 size);
|
||||
|
||||
/// Binds a buffer to the specified binding
|
||||
void BindTexelBuffer(u32 binding, vk::BufferView buffer_view);
|
||||
|
||||
/// Sets the dynamic offset for the uniform buffer at binding
|
||||
void SetBufferOffset(u32 binding, size_t offset);
|
||||
void BindBufferRange(u32 binding, size_t offset);
|
||||
|
||||
private:
|
||||
/// Builds the rasterizer pipeline layout
|
||||
void BuildLayout();
|
||||
|
||||
/// Returns true when the disk data can be used by the current driver
|
||||
bool IsCacheValid(std::span<const u8> cache_data) const;
|
||||
|
||||
@ -101,11 +91,10 @@ private:
|
||||
const Instance& instance;
|
||||
Scheduler& scheduler;
|
||||
RenderpassCache& renderpass_cache;
|
||||
DescriptorPool& pool;
|
||||
DescriptorPool pool;
|
||||
|
||||
Pica::Shader::Profile profile{};
|
||||
vk::UniquePipelineCache pipeline_cache;
|
||||
vk::UniquePipelineLayout pipeline_layout;
|
||||
std::size_t num_worker_threads;
|
||||
Common::ThreadWorker workers;
|
||||
PipelineInfo current_info{};
|
||||
@ -113,18 +102,21 @@ private:
|
||||
tsl::robin_map<u64, std::unique_ptr<GraphicsPipeline>, Common::IdentityHash<u64>>
|
||||
graphics_pipelines;
|
||||
|
||||
std::array<DescriptorSetProvider, NUM_RASTERIZER_SETS> descriptor_set_providers;
|
||||
std::array<DescriptorSetData, NUM_RASTERIZER_SETS> update_data{};
|
||||
std::array<vk::DescriptorSet, NUM_RASTERIZER_SETS> bound_descriptor_sets{};
|
||||
DescriptorSetSpec buffer_set_spec;
|
||||
DescriptorSetSpec utility_set_spec;
|
||||
std::array<DescriptorSetSpec, NUM_PIPELINE_CONFIGS> texture_set_specs;
|
||||
std::array<vk::UniquePipelineLayout, NUM_PIPELINE_CONFIGS> pipeline_layouts;
|
||||
|
||||
std::array<vk::DescriptorSet, NUM_RASTERIZER_SETS> bound_descriptor_sets;
|
||||
std::array<u32, NUM_DYNAMIC_OFFSETS> offsets{};
|
||||
std::bitset<NUM_RASTERIZER_SETS> set_dirty{};
|
||||
|
||||
std::array<u64, MAX_SHADER_STAGES> shader_hashes;
|
||||
std::array<Shader*, MAX_SHADER_STAGES> current_shaders;
|
||||
std::unordered_map<Pica::Shader::Generator::PicaVSConfig, Shader*> programmable_vertex_map;
|
||||
std::unordered_map<std::string, Shader> programmable_vertex_cache;
|
||||
std::unordered_map<Pica::Shader::Generator::PicaFixedGSConfig, Shader> fixed_geometry_shaders;
|
||||
std::unordered_map<Pica::Shader::FSConfig, Shader> fragment_shaders;
|
||||
tsl::robin_map<Pica::Shader::Generator::PicaVSConfig, Shader*> programmable_vertex_map;
|
||||
tsl::robin_map<std::string, Shader> programmable_vertex_cache;
|
||||
tsl::robin_map<Pica::Shader::Generator::PicaFixedGSConfig, Shader> fixed_geometry_shaders;
|
||||
tsl::robin_map<Pica::Shader::FSConfig, Shader> fragment_shaders;
|
||||
Shader trivial_vertex_shader;
|
||||
};
|
||||
|
||||
|
@ -106,8 +106,7 @@ PresentWindow::PresentWindow(Frontend::EmuWindow& emu_window_, const Instance& i
|
||||
vsync_enabled{Settings::values.use_vsync_new.GetValue()},
|
||||
blit_supported{
|
||||
CanBlitToSwapchain(instance.GetPhysicalDevice(), swapchain.GetSurfaceFormat().format)},
|
||||
use_present_thread{Settings::values.async_presentation.GetValue()},
|
||||
last_render_surface{emu_window.GetWindowInfo().render_surface} {
|
||||
use_present_thread{Settings::values.async_presentation.GetValue()} {
|
||||
|
||||
const u32 num_images = swapchain.GetImageCount();
|
||||
const vk::Device device = instance.GetDevice();
|
||||
@ -130,6 +129,7 @@ PresentWindow::PresentWindow(Frontend::EmuWindow& emu_window_, const Instance& i
|
||||
for (u32 i = 0; i < num_images; i++) {
|
||||
Frame& frame = swap_chain[i];
|
||||
frame.cmdbuf = command_buffers[i];
|
||||
frame.index = i;
|
||||
frame.render_ready = device.createSemaphore({});
|
||||
frame.present_done = device.createFence({.flags = vk::FenceCreateFlagBits::eSignaled});
|
||||
free_queue.push(&frame);
|
||||
@ -155,7 +155,7 @@ PresentWindow::~PresentWindow() {
|
||||
}
|
||||
|
||||
void PresentWindow::RecreateFrame(Frame* frame, u32 width, u32 height) {
|
||||
vk::Device device = instance.GetDevice();
|
||||
const vk::Device device = instance.GetDevice();
|
||||
if (frame->framebuffer) {
|
||||
device.destroyFramebuffer(frame->framebuffer);
|
||||
}
|
||||
@ -236,7 +236,7 @@ Frame* PresentWindow::GetRenderFrame() {
|
||||
Frame* frame = free_queue.front();
|
||||
free_queue.pop();
|
||||
|
||||
vk::Device device = instance.GetDevice();
|
||||
const vk::Device device = instance.GetDevice();
|
||||
vk::Result result{};
|
||||
|
||||
const auto wait = [&]() {
|
||||
@ -452,7 +452,7 @@ void PresentWindow::CopyToSwapchain(Frame* frame) {
|
||||
const vk::Semaphore image_acquired = swapchain.GetImageAcquiredSemaphore();
|
||||
const std::array wait_semaphores = {image_acquired, frame->render_ready};
|
||||
|
||||
vk::SubmitInfo submit_info = {
|
||||
const vk::SubmitInfo submit_info = {
|
||||
.waitSemaphoreCount = static_cast<u32>(wait_semaphores.size()),
|
||||
.pWaitSemaphores = wait_semaphores.data(),
|
||||
.pWaitDstStageMask = wait_stage_masks.data(),
|
||||
@ -467,8 +467,7 @@ void PresentWindow::CopyToSwapchain(Frame* frame) {
|
||||
try {
|
||||
graphics_queue.submit(submit_info, frame->present_done);
|
||||
} catch (vk::DeviceLostError& err) {
|
||||
LOG_CRITICAL(Render_Vulkan, "Device lost during present submit: {}", err.what());
|
||||
UNREACHABLE();
|
||||
UNREACHABLE_MSG("Device lost during present submit: {}", err.what());
|
||||
}
|
||||
|
||||
swapchain.Present();
|
||||
|
@ -4,7 +4,6 @@
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include "common/polyfill_thread.h"
|
||||
#include "video_core/renderer_vulkan/vk_swapchain.h"
|
||||
@ -25,6 +24,7 @@ class RenderpassCache;
|
||||
struct Frame {
|
||||
u32 width;
|
||||
u32 height;
|
||||
u32 index;
|
||||
VmaAllocation allocation;
|
||||
vk::Framebuffer framebuffer;
|
||||
vk::Image image;
|
||||
@ -55,12 +55,12 @@ public:
|
||||
/// This is called to notify the rendering backend of a surface change
|
||||
void NotifySurfaceChanged();
|
||||
|
||||
[[nodiscard]] vk::RenderPass Renderpass() const noexcept {
|
||||
vk::RenderPass Renderpass() const noexcept {
|
||||
return present_renderpass;
|
||||
}
|
||||
|
||||
u32 ImageCount() const noexcept {
|
||||
return swapchain.GetImageCount();
|
||||
return swap_chain.size();
|
||||
}
|
||||
|
||||
private:
|
||||
@ -94,7 +94,6 @@ private:
|
||||
bool vsync_enabled{};
|
||||
bool blit_supported;
|
||||
bool use_present_thread{true};
|
||||
void* last_render_surface{};
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
|
@ -89,6 +89,7 @@ RasterizerVulkan::RasterizerVulkan(Memory::MemorySystem& memory,
|
||||
MakeSoftwareVertexLayout();
|
||||
pipeline_info.vertex_layout = software_layout;
|
||||
|
||||
// Create texture buffer views for the lighting LUTs.
|
||||
const vk::Device device = instance.GetDevice();
|
||||
texture_lf_view = device.createBufferViewUnique({
|
||||
.buffer = texture_lf_buffer.Handle(),
|
||||
@ -109,26 +110,14 @@ RasterizerVulkan::RasterizerVulkan(Memory::MemorySystem& memory,
|
||||
.range = VK_WHOLE_SIZE,
|
||||
});
|
||||
|
||||
// Since we don't have access to VK_EXT_descriptor_indexing we need to intiallize
|
||||
// all descriptor sets even the ones we don't use.
|
||||
pipeline_cache.BindBuffer(0, uniform_buffer.Handle(), 0, sizeof(VSPicaUniformData));
|
||||
pipeline_cache.BindBuffer(1, uniform_buffer.Handle(), 0, sizeof(VSUniformData));
|
||||
pipeline_cache.BindBuffer(2, uniform_buffer.Handle(), 0, sizeof(FSUniformData));
|
||||
pipeline_cache.BindTexelBuffer(3, *texture_lf_view);
|
||||
pipeline_cache.BindTexelBuffer(4, *texture_rg_view);
|
||||
pipeline_cache.BindTexelBuffer(5, *texture_rgba_view);
|
||||
|
||||
Surface& null_surface = res_cache.GetSurface(VideoCore::NULL_SURFACE_ID);
|
||||
Surface& null_cube_surface = res_cache.GetSurface(VideoCore::NULL_SURFACE_CUBE_ID);
|
||||
Sampler& null_sampler = res_cache.GetSampler(VideoCore::NULL_SAMPLER_ID);
|
||||
for (u32 i = 0; i < 3; i++) {
|
||||
pipeline_cache.BindTexture(i, null_surface.ImageView(), null_sampler.Handle());
|
||||
}
|
||||
pipeline_cache.BindTexture(3, null_cube_surface.ImageView(), null_sampler.Handle());
|
||||
|
||||
for (u32 i = 0; i < 7; i++) {
|
||||
pipeline_cache.BindStorageImage(i, null_surface.StorageView());
|
||||
}
|
||||
// Update persistent buffer descriptor set with our rasterizer buffers.
|
||||
update_queue.Acquire();
|
||||
update_queue.AddBuffer(uniform_buffer.Handle(), 0, sizeof(VSPicaUniformData));
|
||||
update_queue.AddBuffer(uniform_buffer.Handle(), 0, sizeof(VSPicaUniformData));
|
||||
update_queue.AddBuffer(uniform_buffer.Handle(), 0, sizeof(VSUniformData));
|
||||
update_queue.AddTexelBuffer(texture_lf_view.get());
|
||||
update_queue.AddTexelBuffer(texture_rg_view.get());
|
||||
update_queue.AddTexelBuffer(texture_rgba_view.get());
|
||||
|
||||
SyncEntireState();
|
||||
}
|
||||
@ -478,16 +467,10 @@ bool RasterizerVulkan::Draw(bool accelerate, bool is_indexed) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Update attachment formats
|
||||
pipeline_info.attachments.color = framebuffer->Format(SurfaceType::Color);
|
||||
pipeline_info.attachments.depth = framebuffer->Format(SurfaceType::Depth);
|
||||
|
||||
if (shadow_rendering) {
|
||||
pipeline_cache.BindStorageImage(6, framebuffer->ImageView(SurfaceType::Color));
|
||||
} else {
|
||||
Surface& null_surface = res_cache.GetSurface(VideoCore::NULL_SURFACE_ID);
|
||||
pipeline_cache.BindStorageImage(6, null_surface.StorageView());
|
||||
}
|
||||
|
||||
// Update scissor uniforms
|
||||
const auto [scissor_x1, scissor_y2, scissor_x2, scissor_y1] = fb_helper.Scissor();
|
||||
if (fs_uniform_block_data.data.scissor_x1 != scissor_x1 ||
|
||||
@ -505,6 +488,11 @@ bool RasterizerVulkan::Draw(bool accelerate, bool is_indexed) {
|
||||
// Sync and bind the texture surfaces
|
||||
SyncTextureUnits(framebuffer);
|
||||
|
||||
// Attach the framebuffer as storage image during shadow rendering.
|
||||
if (shadow_rendering) {
|
||||
update_queue.AddImage(framebuffer->ImageView(SurfaceType::Color));
|
||||
}
|
||||
|
||||
// Sync and bind the shader
|
||||
if (shader_dirty) {
|
||||
pipeline_cache.UseFragmentShader(regs, user_config);
|
||||
@ -557,7 +545,17 @@ bool RasterizerVulkan::Draw(bool accelerate, bool is_indexed) {
|
||||
void RasterizerVulkan::SyncTextureUnits(const Framebuffer* framebuffer) {
|
||||
using TextureType = Pica::TexturingRegs::TextureConfig::TextureType;
|
||||
|
||||
// Check if the PICA texture configuration changed.
|
||||
const auto pica_textures = regs.texturing.GetTextures();
|
||||
if (pica_textures == textures && textures[0].config.type != TextureType::TextureCube) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Reserve space in the queue for incoming texture data.
|
||||
// We will write the image data in the same order as defined
|
||||
// in the rasterizer descriptor texture sets.
|
||||
update_queue.Acquire();
|
||||
|
||||
for (u32 texture_index = 0; texture_index < pica_textures.size(); ++texture_index) {
|
||||
const auto& texture = pica_textures[texture_index];
|
||||
|
||||
@ -565,8 +563,7 @@ void RasterizerVulkan::SyncTextureUnits(const Framebuffer* framebuffer) {
|
||||
if (!texture.enabled) {
|
||||
const Surface& null_surface = res_cache.GetSurface(VideoCore::NULL_SURFACE_ID);
|
||||
const Sampler& null_sampler = res_cache.GetSampler(VideoCore::NULL_SAMPLER_ID);
|
||||
pipeline_cache.BindTexture(texture_index, null_surface.ImageView(),
|
||||
null_sampler.Handle());
|
||||
update_queue.AddSampledImage(null_surface.ImageView(), null_sampler.Handle());
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -576,7 +573,7 @@ void RasterizerVulkan::SyncTextureUnits(const Framebuffer* framebuffer) {
|
||||
case TextureType::Shadow2D: {
|
||||
Surface& surface = res_cache.GetTextureSurface(texture);
|
||||
surface.flags |= VideoCore::SurfaceFlagBits::ShadowMap;
|
||||
pipeline_cache.BindStorageImage(0, surface.StorageView());
|
||||
update_queue.AddImage(surface.StorageView());
|
||||
continue;
|
||||
}
|
||||
case TextureType::ShadowCube: {
|
||||
@ -588,7 +585,6 @@ void RasterizerVulkan::SyncTextureUnits(const Framebuffer* framebuffer) {
|
||||
continue;
|
||||
}
|
||||
default:
|
||||
UnbindSpecial();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -596,10 +592,12 @@ void RasterizerVulkan::SyncTextureUnits(const Framebuffer* framebuffer) {
|
||||
// Bind the texture provided by the rasterizer cache
|
||||
Surface& surface = res_cache.GetTextureSurface(texture);
|
||||
Sampler& sampler = res_cache.GetSampler(texture.config);
|
||||
if (!IsFeedbackLoop(texture_index, framebuffer, surface, sampler)) {
|
||||
pipeline_cache.BindTexture(texture_index, surface.ImageView(), sampler.Handle());
|
||||
if (!IsFeedbackLoop(framebuffer, surface, sampler)) {
|
||||
update_queue.AddSampledImage(surface.ImageView(), sampler.Handle());
|
||||
}
|
||||
}
|
||||
|
||||
textures = pica_textures;
|
||||
}
|
||||
|
||||
void RasterizerVulkan::BindShadowCube(const Pica::TexturingRegs::FullTextureConfig& texture) {
|
||||
@ -611,13 +609,11 @@ void RasterizerVulkan::BindShadowCube(const Pica::TexturingRegs::FullTextureConf
|
||||
};
|
||||
|
||||
for (CubeFace face : faces) {
|
||||
const u32 binding = static_cast<u32>(face);
|
||||
info.physical_address = regs.texturing.GetCubePhysicalAddress(face);
|
||||
|
||||
const VideoCore::SurfaceId surface_id = res_cache.GetTextureSurface(info);
|
||||
const auto surface_id = res_cache.GetTextureSurface(info);
|
||||
Surface& surface = res_cache.GetSurface(surface_id);
|
||||
surface.flags |= VideoCore::SurfaceFlagBits::ShadowMap;
|
||||
pipeline_cache.BindStorageImage(binding, surface.StorageView());
|
||||
update_queue.AddImage(surface.StorageView());
|
||||
}
|
||||
}
|
||||
|
||||
@ -635,13 +631,13 @@ void RasterizerVulkan::BindTextureCube(const Pica::TexturingRegs::FullTextureCon
|
||||
.format = texture.format,
|
||||
};
|
||||
|
||||
Surface& surface = res_cache.GetTextureCube(config);
|
||||
Sampler& sampler = res_cache.GetSampler(texture.config);
|
||||
pipeline_cache.BindTexture(3, surface.ImageView(), sampler.Handle());
|
||||
const Surface& surface = res_cache.GetTextureCube(config);
|
||||
const Sampler& sampler = res_cache.GetSampler(texture.config);
|
||||
update_queue.AddSampledImage(surface.ImageView(), sampler.Handle());
|
||||
}
|
||||
|
||||
bool RasterizerVulkan::IsFeedbackLoop(u32 texture_index, const Framebuffer* framebuffer,
|
||||
Surface& surface, Sampler& sampler) {
|
||||
bool RasterizerVulkan::IsFeedbackLoop(const Framebuffer* framebuffer, Surface& surface,
|
||||
Sampler& sampler) {
|
||||
const vk::ImageView color_view = framebuffer->ImageView(SurfaceType::Color);
|
||||
const bool is_feedback_loop = color_view == surface.ImageView();
|
||||
if (!is_feedback_loop) {
|
||||
@ -649,20 +645,10 @@ bool RasterizerVulkan::IsFeedbackLoop(u32 texture_index, const Framebuffer* fram
|
||||
}
|
||||
|
||||
// Make a temporary copy of the framebuffer to sample from
|
||||
pipeline_cache.BindTexture(texture_index, surface.CopyImageView(), sampler.Handle());
|
||||
update_queue.AddSampledImage(surface.CopyImageView(), sampler.Handle());
|
||||
return true;
|
||||
}
|
||||
|
||||
void RasterizerVulkan::UnbindSpecial() {
|
||||
Surface& null_surface = res_cache.GetSurface(VideoCore::NULL_SURFACE_ID);
|
||||
const Surface& null_cube_surface = res_cache.GetSurface(VideoCore::NULL_SURFACE_CUBE_ID);
|
||||
const Sampler& null_sampler = res_cache.GetSampler(VideoCore::NULL_SAMPLER_ID);
|
||||
pipeline_cache.BindTexture(3, null_cube_surface.ImageView(), null_sampler.Handle());
|
||||
for (u32 i = 0; i < 6; i++) {
|
||||
pipeline_cache.BindStorageImage(i, null_surface.StorageView());
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerVulkan::NotifyFixedFunctionPicaRegisterChanged(u32 id) {
|
||||
switch (id) {
|
||||
// Culling
|
||||
@ -1103,24 +1089,24 @@ void RasterizerVulkan::UploadUniforms(bool accelerate_draw) {
|
||||
auto [uniforms, offset, invalidate] =
|
||||
uniform_buffer.Map(uniform_size, uniform_buffer_alignment);
|
||||
|
||||
u32 used_bytes = 0;
|
||||
size_t used_bytes = 0;
|
||||
|
||||
if (sync_vs || invalidate) {
|
||||
std::memcpy(uniforms + used_bytes, &vs_uniform_block_data.data,
|
||||
sizeof(vs_uniform_block_data.data));
|
||||
|
||||
pipeline_cache.SetBufferOffset(1, offset + used_bytes);
|
||||
pipeline_cache.BindBufferRange(1, offset + used_bytes);
|
||||
vs_uniform_block_data.dirty = false;
|
||||
used_bytes += static_cast<u32>(uniform_size_aligned_vs);
|
||||
used_bytes += uniform_size_aligned_vs;
|
||||
}
|
||||
|
||||
if (sync_fs || invalidate) {
|
||||
std::memcpy(uniforms + used_bytes, &fs_uniform_block_data.data,
|
||||
sizeof(fs_uniform_block_data.data));
|
||||
|
||||
pipeline_cache.SetBufferOffset(2, offset + used_bytes);
|
||||
pipeline_cache.BindBufferRange(2, offset + used_bytes);
|
||||
fs_uniform_block_data.dirty = false;
|
||||
used_bytes += static_cast<u32>(uniform_size_aligned_fs);
|
||||
used_bytes += uniform_size_aligned_fs;
|
||||
}
|
||||
|
||||
if (sync_vs_pica) {
|
||||
@ -1128,8 +1114,8 @@ void RasterizerVulkan::UploadUniforms(bool accelerate_draw) {
|
||||
vs_uniforms.uniforms.SetFromRegs(regs.vs, Pica::g_state.vs);
|
||||
std::memcpy(uniforms + used_bytes, &vs_uniforms, sizeof(vs_uniforms));
|
||||
|
||||
pipeline_cache.SetBufferOffset(0, offset + used_bytes);
|
||||
used_bytes += static_cast<u32>(uniform_size_aligned_vs_pica);
|
||||
pipeline_cache.BindBufferRange(0, offset + used_bytes);
|
||||
used_bytes += uniform_size_aligned_vs_pica;
|
||||
}
|
||||
|
||||
uniform_buffer.Commit(used_bytes);
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
#include "core/hw/gpu.h"
|
||||
#include "video_core/rasterizer_accelerated.h"
|
||||
#include "video_core/renderer_vulkan/vk_descriptor_update.h"
|
||||
#include "video_core/renderer_vulkan/vk_pipeline_cache.h"
|
||||
#include "video_core/renderer_vulkan/vk_renderpass_cache.h"
|
||||
#include "video_core/renderer_vulkan/vk_stream_buffer.h"
|
||||
@ -104,11 +105,7 @@ private:
|
||||
void BindTextureCube(const Pica::TexturingRegs::FullTextureConfig& texture);
|
||||
|
||||
/// Makes a temporary copy of the framebuffer if a feedback loop is detected
|
||||
bool IsFeedbackLoop(u32 texture_index, const Framebuffer* framebuffer, Surface& surface,
|
||||
Sampler& sampler);
|
||||
|
||||
/// Unbinds all special texture unit 0 texture configurations
|
||||
void UnbindSpecial();
|
||||
bool IsFeedbackLoop(const Framebuffer* framebuffer, Surface& surface, Sampler& sampler);
|
||||
|
||||
/// Upload the uniform blocks to the uniform buffer object
|
||||
void UploadUniforms(bool accelerate_draw);
|
||||
@ -144,6 +141,7 @@ private:
|
||||
PipelineCache pipeline_cache;
|
||||
TextureRuntime runtime;
|
||||
RasterizerCache res_cache;
|
||||
DescriptorUpdateQueue update_queue;
|
||||
|
||||
VertexLayout software_layout;
|
||||
std::array<u32, 16> binding_offsets{};
|
||||
@ -159,6 +157,7 @@ private:
|
||||
vk::UniqueBufferView texture_lf_view;
|
||||
vk::UniqueBufferView texture_rg_view;
|
||||
vk::UniqueBufferView texture_rgba_view;
|
||||
Pica::TexturingRegs::Textures textures{};
|
||||
u64 uniform_buffer_alignment;
|
||||
u64 uniform_size_aligned_vs_pica;
|
||||
u64 uniform_size_aligned_vs;
|
||||
|
@ -1,113 +1,147 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cstddef>
|
||||
#include <optional>
|
||||
#include "video_core/renderer_vulkan/vk_instance.h"
|
||||
#include "video_core/renderer_vulkan/vk_master_semaphore.h"
|
||||
#include "video_core/renderer_vulkan/vk_resource_pool.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
ResourcePool::ResourcePool(MasterSemaphore* master_semaphore_, size_t grow_step_)
|
||||
: master_semaphore{master_semaphore_}, grow_step{grow_step_} {}
|
||||
ResourcePool::ResourcePool(MasterSemaphore* master_semaphore_)
|
||||
: master_semaphore{master_semaphore_} {}
|
||||
|
||||
std::size_t ResourcePool::CommitResource() {
|
||||
ResourcePool::~ResourcePool() = default;
|
||||
|
||||
s64 ResourcePool::CommitResource() {
|
||||
// Refresh semaphore to query updated results
|
||||
master_semaphore->Refresh();
|
||||
const u64 gpu_tick = master_semaphore->KnownGpuTick();
|
||||
const auto search = [this, gpu_tick](std::size_t begin,
|
||||
std::size_t end) -> std::optional<std::size_t> {
|
||||
for (std::size_t iterator = begin; iterator < end; ++iterator) {
|
||||
if (gpu_tick >= ticks[iterator]) {
|
||||
ticks[iterator] = master_semaphore->CurrentTick();
|
||||
return iterator;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
};
|
||||
|
||||
// Try to find a free resource from the hinted position to the end.
|
||||
std::optional<std::size_t> found = search(hint_iterator, ticks.size());
|
||||
if (!found) {
|
||||
// Search from beginning to the hinted position.
|
||||
found = search(0, hint_iterator);
|
||||
if (!found) {
|
||||
// Both searches failed, the pool is full; handle it.
|
||||
const std::size_t free_resource = ManageOverflow();
|
||||
// Update the last used tick of the previous resource.
|
||||
if (last_index != -1) {
|
||||
ticks[last_index] = master_semaphore->CurrentTick();
|
||||
}
|
||||
|
||||
ticks[free_resource] = master_semaphore->CurrentTick();
|
||||
found = free_resource;
|
||||
// Try to find a free resource.
|
||||
size_t found = ticks.size();
|
||||
for (size_t index = 0; index < ticks.size(); index++) {
|
||||
if (gpu_tick >= ticks[index]) {
|
||||
found = index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Free iterator is hinted to the resource after the one that's been commited.
|
||||
hint_iterator = (*found + 1) % ticks.size();
|
||||
return *found;
|
||||
// The pool is full; handle it.
|
||||
if (found == ticks.size()) {
|
||||
ticks.resize(found + 1);
|
||||
Allocate();
|
||||
}
|
||||
|
||||
// Return found resource.
|
||||
last_index = found;
|
||||
return found;
|
||||
}
|
||||
|
||||
std::size_t ResourcePool::ManageOverflow() {
|
||||
const std::size_t old_capacity = ticks.size();
|
||||
Grow();
|
||||
|
||||
// The last entry is guaranted to be free, since it's the first element of the freshly
|
||||
// allocated resources.
|
||||
return old_capacity;
|
||||
}
|
||||
|
||||
void ResourcePool::Grow() {
|
||||
const size_t old_capacity = ticks.size();
|
||||
ticks.resize(old_capacity + grow_step);
|
||||
Allocate(old_capacity, old_capacity + grow_step);
|
||||
}
|
||||
|
||||
constexpr size_t COMMAND_BUFFER_POOL_SIZE = 4;
|
||||
|
||||
struct CommandPool::Pool {
|
||||
vk::CommandPool handle;
|
||||
std::array<vk::CommandBuffer, COMMAND_BUFFER_POOL_SIZE> cmdbufs;
|
||||
};
|
||||
|
||||
CommandPool::CommandPool(const Instance& instance, MasterSemaphore* master_semaphore)
|
||||
: ResourcePool{master_semaphore, COMMAND_BUFFER_POOL_SIZE}, instance{instance} {}
|
||||
|
||||
CommandPool::~CommandPool() {
|
||||
vk::Device device = instance.GetDevice();
|
||||
for (Pool& pool : pools) {
|
||||
device.destroyCommandPool(pool.handle);
|
||||
}
|
||||
}
|
||||
|
||||
void CommandPool::Allocate(std::size_t begin, std::size_t end) {
|
||||
// Command buffers are going to be commited, recorded, executed every single usage cycle.
|
||||
// They are also going to be reseted when commited.
|
||||
Pool& pool = pools.emplace_back();
|
||||
|
||||
: ResourcePool{master_semaphore}, device{instance.GetDevice()} {
|
||||
const vk::CommandPoolCreateInfo pool_create_info = {
|
||||
.flags = vk::CommandPoolCreateFlagBits::eTransient |
|
||||
vk::CommandPoolCreateFlagBits::eResetCommandBuffer,
|
||||
.queueFamilyIndex = instance.GetGraphicsQueueFamilyIndex(),
|
||||
};
|
||||
cmdpool = device.createCommandPoolUnique(pool_create_info);
|
||||
}
|
||||
|
||||
vk::Device device = instance.GetDevice();
|
||||
pool.handle = device.createCommandPool(pool_create_info);
|
||||
CommandPool::~CommandPool() = default;
|
||||
|
||||
void CommandPool::Allocate() {
|
||||
// Command buffers are going to be commited, recorded, executed every single usage cycle.
|
||||
// They are also going to be reseted when commited.
|
||||
auto& cmdbuf = cmdbuffers.emplace_back();
|
||||
|
||||
const vk::CommandBufferAllocateInfo buffer_alloc_info = {
|
||||
.commandPool = pool.handle,
|
||||
.commandPool = cmdpool.get(),
|
||||
.level = vk::CommandBufferLevel::ePrimary,
|
||||
.commandBufferCount = COMMAND_BUFFER_POOL_SIZE,
|
||||
.commandBufferCount = 1,
|
||||
};
|
||||
|
||||
auto buffers = device.allocateCommandBuffers(buffer_alloc_info);
|
||||
std::copy(buffers.begin(), buffers.end(), pool.cmdbufs.begin());
|
||||
const auto buffers = device.allocateCommandBuffers(buffer_alloc_info);
|
||||
cmdbuf = buffers[0];
|
||||
}
|
||||
|
||||
vk::CommandBuffer CommandPool::Commit() {
|
||||
const std::size_t index = CommitResource();
|
||||
const auto pool_index = index / COMMAND_BUFFER_POOL_SIZE;
|
||||
const auto sub_index = index % COMMAND_BUFFER_POOL_SIZE;
|
||||
return pools[pool_index].cmdbufs[sub_index];
|
||||
const size_t index = CommitResource();
|
||||
return cmdbuffers[index];
|
||||
}
|
||||
|
||||
constexpr size_t MAX_BATCH_SIZE = 8;
|
||||
|
||||
DescriptorPool::DescriptorPool(const Instance& instance, MasterSemaphore* master_semaphore)
|
||||
: ResourcePool{master_semaphore}, device{instance.GetDevice()} {
|
||||
// Ensure we have at least one pool available.
|
||||
CommitResource();
|
||||
}
|
||||
|
||||
DescriptorPool::~DescriptorPool() = default;
|
||||
|
||||
void DescriptorPool::Allocate() {
|
||||
// Descriptor pools are going to be commited and used for descriptor allocation.
|
||||
// When out of memory, a new pool is allocated or an old is reused
|
||||
auto& pool = pools.emplace_back();
|
||||
|
||||
static constexpr std::array<vk::DescriptorPoolSize, 6> pool_sizes = {{
|
||||
{vk::DescriptorType::eUniformBufferDynamic, 64},
|
||||
{vk::DescriptorType::eUniformTexelBuffer, 64},
|
||||
{vk::DescriptorType::eCombinedImageSampler, 4096},
|
||||
{vk::DescriptorType::eSampledImage, 256},
|
||||
{vk::DescriptorType::eStorageImage, 256},
|
||||
{vk::DescriptorType::eStorageBuffer, 32},
|
||||
}};
|
||||
|
||||
const vk::DescriptorPoolCreateInfo descriptor_pool_info = {
|
||||
.maxSets = 4098,
|
||||
.poolSizeCount = static_cast<u32>(pool_sizes.size()),
|
||||
.pPoolSizes = pool_sizes.data(),
|
||||
};
|
||||
|
||||
pool = device.createDescriptorPoolUnique(descriptor_pool_info);
|
||||
}
|
||||
|
||||
std::vector<vk::DescriptorSet> DescriptorPool::Commit(vk::DescriptorSetLayout layout,
|
||||
u32 num_sets) {
|
||||
ASSERT_MSG(num_sets <= MAX_BATCH_SIZE, "Cannot allocate more than {} descriptor sets",
|
||||
MAX_BATCH_SIZE);
|
||||
|
||||
// Fill array with the layout handle
|
||||
std::array<vk::DescriptorSetLayout, MAX_BATCH_SIZE> layouts;
|
||||
layouts.fill(layout);
|
||||
|
||||
vk::DescriptorSetAllocateInfo alloc_info = {
|
||||
.descriptorPool = pools[last_index].get(),
|
||||
.descriptorSetCount = num_sets,
|
||||
.pSetLayouts = layouts.data(),
|
||||
};
|
||||
|
||||
// Attempt to allocate the descriptor sets.
|
||||
try {
|
||||
return device.allocateDescriptorSets(alloc_info);
|
||||
} catch (const vk::OutOfPoolMemoryError&) {
|
||||
// If out of memory switch to a new pool.
|
||||
const size_t index = CommitResource();
|
||||
const auto new_pool = pools[index].get();
|
||||
device.resetDescriptorPool(new_pool);
|
||||
alloc_info.descriptorPool = new_pool;
|
||||
}
|
||||
|
||||
// This time the allocation should succeed.
|
||||
return device.allocateDescriptorSets(alloc_info);
|
||||
}
|
||||
|
||||
vk::DescriptorSet DescriptorPool::Commit(vk::DescriptorSetLayout layout) {
|
||||
const auto sets = Commit(layout, 1);
|
||||
return sets[0];
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@ -13,40 +13,19 @@ namespace Vulkan {
|
||||
class Instance;
|
||||
class MasterSemaphore;
|
||||
|
||||
/**
|
||||
* Handles a pool of resources protected by fences. Manages resource overflow allocating more
|
||||
* resources.
|
||||
*/
|
||||
class ResourcePool {
|
||||
public:
|
||||
explicit ResourcePool() = default;
|
||||
explicit ResourcePool(MasterSemaphore* master_semaphore, std::size_t grow_step);
|
||||
virtual ~ResourcePool() = default;
|
||||
|
||||
ResourcePool& operator=(ResourcePool&&) noexcept = default;
|
||||
ResourcePool(ResourcePool&&) noexcept = default;
|
||||
|
||||
ResourcePool& operator=(const ResourcePool&) = default;
|
||||
ResourcePool(const ResourcePool&) = default;
|
||||
explicit ResourcePool(MasterSemaphore* master_semaphore);
|
||||
virtual ~ResourcePool();
|
||||
|
||||
protected:
|
||||
std::size_t CommitResource();
|
||||
|
||||
/// Called when a chunk of resources have to be allocated.
|
||||
virtual void Allocate(std::size_t begin, std::size_t end) = 0;
|
||||
|
||||
private:
|
||||
/// Manages pool overflow allocating new resources.
|
||||
std::size_t ManageOverflow();
|
||||
|
||||
/// Allocates a new page of resources.
|
||||
void Grow();
|
||||
s64 CommitResource();
|
||||
virtual void Allocate() = 0;
|
||||
|
||||
protected:
|
||||
MasterSemaphore* master_semaphore{nullptr};
|
||||
std::size_t grow_step = 0; ///< Number of new resources created after an overflow
|
||||
std::size_t hint_iterator = 0; ///< Hint to where the next free resources is likely to be found
|
||||
std::vector<u64> ticks; ///< Ticks for each resource
|
||||
s64 last_index = -1; ///< Hint to where the last commited resource was found
|
||||
std::vector<u64> ticks; ///< Ticks each resource was last used
|
||||
};
|
||||
|
||||
class CommandPool final : public ResourcePool {
|
||||
@ -54,14 +33,31 @@ public:
|
||||
explicit CommandPool(const Instance& instance, MasterSemaphore* master_semaphore);
|
||||
~CommandPool() override;
|
||||
|
||||
void Allocate(std::size_t begin, std::size_t end) override;
|
||||
void Allocate() override;
|
||||
|
||||
vk::CommandBuffer Commit();
|
||||
|
||||
private:
|
||||
struct Pool;
|
||||
const Instance& instance;
|
||||
std::vector<Pool> pools;
|
||||
vk::Device device;
|
||||
vk::UniqueCommandPool cmdpool;
|
||||
std::vector<vk::CommandBuffer> cmdbuffers;
|
||||
};
|
||||
|
||||
class DescriptorPool : public ResourcePool {
|
||||
public:
|
||||
explicit DescriptorPool(const Instance& instance, MasterSemaphore* master_semaphore);
|
||||
~DescriptorPool();
|
||||
|
||||
std::vector<vk::DescriptorSet> Commit(vk::DescriptorSetLayout layout, u32 num_sets);
|
||||
|
||||
vk::DescriptorSet Commit(vk::DescriptorSetLayout layout);
|
||||
|
||||
private:
|
||||
void Allocate() override;
|
||||
|
||||
private:
|
||||
vk::Device device;
|
||||
std::vector<vk::UniqueDescriptorPool> pools;
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
|
@ -56,6 +56,10 @@ public:
|
||||
return image_count;
|
||||
}
|
||||
|
||||
u32 GetImageIndex() const {
|
||||
return image_index;
|
||||
}
|
||||
|
||||
vk::Extent2D GetExtent() const {
|
||||
return extent;
|
||||
}
|
||||
|
@ -11,7 +11,6 @@
|
||||
#include "video_core/rasterizer_cache/texture_codec.h"
|
||||
#include "video_core/rasterizer_cache/utils.h"
|
||||
#include "video_core/renderer_vulkan/pica_to_vk.h"
|
||||
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
|
||||
#include "video_core/renderer_vulkan/vk_instance.h"
|
||||
#include "video_core/renderer_vulkan/vk_renderpass_cache.h"
|
||||
#include "video_core/renderer_vulkan/vk_scheduler.h"
|
||||
@ -253,9 +252,9 @@ constexpr u64 DOWNLOAD_BUFFER_SIZE = 16_MiB;
|
||||
|
||||
TextureRuntime::TextureRuntime(const Instance& instance, Scheduler& scheduler,
|
||||
RenderpassCache& renderpass_cache, DescriptorPool& pool,
|
||||
DescriptorSetProvider& texture_provider_, u32 num_swapchain_images_)
|
||||
u32 num_swapchain_images_)
|
||||
: instance{instance}, scheduler{scheduler}, renderpass_cache{renderpass_cache},
|
||||
texture_provider{texture_provider_}, blit_helper{instance, scheduler, pool, renderpass_cache},
|
||||
blit_helper{instance, scheduler, pool, renderpass_cache},
|
||||
upload_buffer{instance, scheduler, vk::BufferUsageFlagBits::eTransferSrc, UPLOAD_BUFFER_SIZE,
|
||||
BufferType::Upload},
|
||||
download_buffer{instance, scheduler,
|
||||
@ -697,13 +696,6 @@ bool TextureRuntime::NeedsConversion(VideoCore::PixelFormat format) const {
|
||||
traits.aspect != (vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil);
|
||||
}
|
||||
|
||||
void TextureRuntime::FreeDescriptorSetsWithImage(vk::ImageView image_view) {
|
||||
texture_provider.FreeWithImage(image_view);
|
||||
blit_helper.compute_provider.FreeWithImage(image_view);
|
||||
blit_helper.compute_buffer_provider.FreeWithImage(image_view);
|
||||
blit_helper.two_textures_provider.FreeWithImage(image_view);
|
||||
}
|
||||
|
||||
Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceParams& params)
|
||||
: SurfaceBase{params}, runtime{&runtime_}, instance{&runtime_.GetInstance()},
|
||||
scheduler{&runtime_.GetScheduler()}, traits{instance->GetTraits(pixel_format)} {
|
||||
@ -803,9 +795,6 @@ Surface::~Surface() {
|
||||
return;
|
||||
}
|
||||
for (const auto& [alloc, image, image_view] : handles) {
|
||||
if (image_view) {
|
||||
runtime->FreeDescriptorSetsWithImage(*image_view);
|
||||
}
|
||||
if (image) {
|
||||
vmaDestroyImage(instance->GetAllocator(), image, alloc);
|
||||
}
|
||||
@ -1558,13 +1547,13 @@ Sampler::Sampler(TextureRuntime& runtime, const VideoCore::SamplerParams& params
|
||||
|
||||
Sampler::~Sampler() = default;
|
||||
|
||||
DebugScope::DebugScope(TextureRuntime& runtime, Common::Vec4f color, std::string_view label)
|
||||
DebugScope::DebugScope(TextureRuntime& runtime, Common::Vec4f color, std::string&& label)
|
||||
: scheduler{runtime.GetScheduler()}, has_debug_tool{
|
||||
runtime.GetInstance().HasDebuggingToolAttached()} {
|
||||
if (!has_debug_tool) {
|
||||
return;
|
||||
}
|
||||
scheduler.Record([color, label = std::string(label)](vk::CommandBuffer cmdbuf) {
|
||||
scheduler.Record([color, label = std::move(label)](vk::CommandBuffer cmdbuf) {
|
||||
const vk::DebugUtilsLabelEXT debug_label = {
|
||||
.pLabelName = label.data(),
|
||||
.color = std::array{color[0], color[1], color[2], color[3]},
|
||||
|
@ -42,8 +42,8 @@ class TextureRuntime {
|
||||
|
||||
public:
|
||||
explicit TextureRuntime(const Instance& instance, Scheduler& scheduler,
|
||||
RenderpassCache& renderpass_cache, DescriptorPool& pool,
|
||||
DescriptorSetProvider& texture_provider, u32 num_swapchain_images);
|
||||
RenderpassCache& renderpass_cache,
|
||||
u32 num_swapchain_images);
|
||||
~TextureRuntime();
|
||||
|
||||
const Instance& GetInstance() const {
|
||||
@ -85,9 +85,6 @@ public:
|
||||
/// Returns true if the provided pixel format needs convertion
|
||||
bool NeedsConversion(VideoCore::PixelFormat format) const;
|
||||
|
||||
/// Removes any descriptor sets that contain the provided image view.
|
||||
void FreeDescriptorSetsWithImage(vk::ImageView image_view);
|
||||
|
||||
private:
|
||||
/// Clears a partial texture rect using a clear rectangle
|
||||
void ClearTextureWithRenderpass(Surface& surface, const VideoCore::TextureClear& clear);
|
||||
@ -96,7 +93,6 @@ private:
|
||||
const Instance& instance;
|
||||
Scheduler& scheduler;
|
||||
RenderpassCache& renderpass_cache;
|
||||
DescriptorSetProvider& texture_provider;
|
||||
BlitHelper blit_helper;
|
||||
StreamBuffer upload_buffer;
|
||||
StreamBuffer download_buffer;
|
||||
@ -277,7 +273,7 @@ public:
|
||||
explicit DebugScope(TextureRuntime& runtime, Common::Vec4f color,
|
||||
fmt::format_string<T...> format, T... args)
|
||||
: DebugScope{runtime, color, fmt::format(format, std::forward<T>(args)...)} {}
|
||||
explicit DebugScope(TextureRuntime& runtime, Common::Vec4f color, std::string_view label);
|
||||
explicit DebugScope(TextureRuntime& runtime, Common::Vec4f color, std::string&& label);
|
||||
~DebugScope();
|
||||
|
||||
private:
|
||||
|
@ -10,6 +10,7 @@ using ProcTexClamp = TexturingRegs::ProcTexClamp;
|
||||
using ProcTexShift = TexturingRegs::ProcTexShift;
|
||||
using ProcTexCombiner = TexturingRegs::ProcTexCombiner;
|
||||
using ProcTexFilter = TexturingRegs::ProcTexFilter;
|
||||
using TextureType = Pica::TexturingRegs::TextureConfig::TextureType;
|
||||
|
||||
constexpr static size_t RESERVE_SIZE = 8 * 1024 * 1024;
|
||||
|
||||
@ -1265,7 +1266,7 @@ void FragmentModule::DefineExtensions() {
|
||||
out += "#extension GL_ARM_shader_framebuffer_fetch : enable\n";
|
||||
out += "#define destFactor gl_LastFragColorARM\n";
|
||||
} else {
|
||||
out += "#define destFactor texelFetch(color_buffer, ivec2(gl_FragCoord.xy), 0)\n";
|
||||
out += "#define destFactor texelFetch(tex_color, ivec2(gl_FragCoord.xy), 0)\n";
|
||||
use_blend_fallback = true;
|
||||
}
|
||||
}
|
||||
@ -1301,27 +1302,32 @@ void FragmentModule::DefineInterface() {
|
||||
}
|
||||
|
||||
void FragmentModule::DefineBindings() {
|
||||
// Uniform and texture buffers
|
||||
out += FSUniformBlockDef;
|
||||
out += "layout(binding = 3) uniform samplerBuffer texture_buffer_lut_lf;\n";
|
||||
out += "layout(binding = 4) uniform samplerBuffer texture_buffer_lut_rg;\n";
|
||||
out += "layout(binding = 5) uniform samplerBuffer texture_buffer_lut_rgba;\n\n";
|
||||
|
||||
const std::string_view texunit_set = profile.is_vulkan ? "set = 1, " : "";
|
||||
// Texture samplers
|
||||
const auto texunit_set = profile.is_vulkan ? "set = 1, " : "";
|
||||
const auto texture_type = config.texture.texture0_type.Value();
|
||||
for (u32 i = 0; i < 3; i++) {
|
||||
out += fmt::format("layout({0}binding = {1}) uniform sampler2D tex{1};\n", texunit_set, i);
|
||||
const auto sampler =
|
||||
i == 0 && texture_type == TextureType::TextureCube ? "samplerCube" : "sampler2D";
|
||||
out +=
|
||||
fmt::format("layout({0}binding = {1}) uniform {2} tex{1};\n", texunit_set, i, sampler);
|
||||
}
|
||||
|
||||
out += fmt::format("layout({}binding = 3) uniform samplerCube tex_cube;\n\n", texunit_set);
|
||||
|
||||
if (config.user.use_custom_normal && !profile.is_vulkan) {
|
||||
out += "layout(binding = 7) uniform sampler2D tex_normal;\n";
|
||||
out += "layout(binding = 6) uniform sampler2D tex_normal;\n";
|
||||
}
|
||||
if (use_blend_fallback && !profile.is_vulkan) {
|
||||
out += "layout(location = 10) uniform sampler2D color_buffer;\n";
|
||||
out += "layout(location = 7) uniform sampler2D tex_color;\n";
|
||||
}
|
||||
|
||||
// Storage images
|
||||
static constexpr std::array postfixes = {"px", "nx", "py", "ny", "pz", "nz"};
|
||||
const std::string_view shadow_set = profile.is_vulkan ? "set = 2, " : "";
|
||||
const auto shadow_set = profile.is_vulkan ? "set = 2, " : "";
|
||||
for (u32 i = 0; i < postfixes.size(); i++) {
|
||||
out += fmt::format(
|
||||
"layout({}binding = {}, r32ui) uniform readonly uimage2D shadow_texture_{};\n",
|
||||
@ -1591,7 +1597,7 @@ void FragmentModule::DefineTexUnitSampler(u32 texture_unit) {
|
||||
out += "return textureProj(tex0, vec3(texcoord0, texcoord0_w));";
|
||||
break;
|
||||
case TexturingRegs::TextureConfig::TextureCube:
|
||||
out += "return texture(tex_cube, vec3(texcoord0, texcoord0_w));";
|
||||
out += "return texture(tex0, vec3(texcoord0, texcoord0_w));";
|
||||
break;
|
||||
case TexturingRegs::TextureConfig::Shadow2D:
|
||||
out += "return shadowTexture(texcoord0, texcoord0_w);";
|
||||
|
@ -12,6 +12,7 @@ using Pica::LightingRegs;
|
||||
using Pica::RasterizerRegs;
|
||||
using Pica::TexturingRegs;
|
||||
using TevStageConfig = TexturingRegs::TevStageConfig;
|
||||
using TextureType = TexturingRegs::TextureConfig::TextureType;
|
||||
|
||||
constexpr u32 SPIRV_VERSION_1_3 = 0x00010300;
|
||||
|
||||
@ -977,7 +978,7 @@ void FragmentModule::DefineTexSampler(u32 texture_unit) {
|
||||
};
|
||||
|
||||
const auto sample_3d = [&](Id tex_id, bool projection) {
|
||||
const Id image_type = tex_id.value == tex_cube_id.value ? image_cube_id : image2d_id;
|
||||
const Id image_type = !projection ? image_cube_id : image2d_id;
|
||||
const Id sampled_image{OpLoad(TypeSampledImage(image_type), tex_id)};
|
||||
const Id texcoord0_w{OpLoad(f32_id, texcoord0_w_id)};
|
||||
const Id coord{OpCompositeConstruct(vec_ids.Get(3), OpCompositeExtract(f32_id, texcoord, 0),
|
||||
@ -1001,7 +1002,7 @@ void FragmentModule::DefineTexSampler(u32 texture_unit) {
|
||||
ret_val = sample_3d(tex0_id, true);
|
||||
break;
|
||||
case Pica::TexturingRegs::TextureConfig::TextureCube:
|
||||
ret_val = sample_3d(tex_cube_id, false);
|
||||
ret_val = sample_3d(tex0_id, false);
|
||||
break;
|
||||
case Pica::TexturingRegs::TextureConfig::Shadow2D:
|
||||
ret_val = SampleShadow();
|
||||
@ -1564,20 +1565,24 @@ void FragmentModule::DefineInterface() {
|
||||
view_id = DefineInput(vec_ids.Get(3), 7);
|
||||
color_id = DefineOutput(vec_ids.Get(4), 0);
|
||||
|
||||
// Define the texture unit samplers/uniforms
|
||||
// Define the texture unit samplers types
|
||||
image_buffer_id = TypeImage(f32_id, spv::Dim::Buffer, 0, 0, 0, 1, spv::ImageFormat::Unknown);
|
||||
image2d_id = TypeImage(f32_id, spv::Dim::Dim2D, 0, 0, 0, 1, spv::ImageFormat::Unknown);
|
||||
image_cube_id = TypeImage(f32_id, spv::Dim::Cube, 0, 0, 0, 1, spv::ImageFormat::Unknown);
|
||||
image_r32_id = TypeImage(u32_id, spv::Dim::Dim2D, 0, 0, 0, 2, spv::ImageFormat::R32ui);
|
||||
sampler_id = TypeSampler();
|
||||
|
||||
// Define lighting texture buffers
|
||||
texture_buffer_lut_lf_id = DefineUniformConst(image_buffer_id, 0, 3);
|
||||
texture_buffer_lut_rg_id = DefineUniformConst(image_buffer_id, 0, 4);
|
||||
texture_buffer_lut_rgba_id = DefineUniformConst(image_buffer_id, 0, 5);
|
||||
tex0_id = DefineUniformConst(TypeSampledImage(image2d_id), 1, 0);
|
||||
|
||||
// Define texture unit samplers
|
||||
const auto texture_type = config.texture.texture0_type.Value();
|
||||
const auto tex0_type = texture_type == TextureType::TextureCube ? image_cube_id : image2d_id;
|
||||
tex0_id = DefineUniformConst(TypeSampledImage(tex0_type), 1, 0);
|
||||
tex1_id = DefineUniformConst(TypeSampledImage(image2d_id), 1, 1);
|
||||
tex2_id = DefineUniformConst(TypeSampledImage(image2d_id), 1, 2);
|
||||
tex_cube_id = DefineUniformConst(TypeSampledImage(image_cube_id), 1, 3);
|
||||
|
||||
// Define shadow textures
|
||||
shadow_texture_px_id = DefineUniformConst(image_r32_id, 2, 0, true);
|
||||
|
@ -252,7 +252,6 @@ private:
|
||||
Id tex0_id{};
|
||||
Id tex1_id{};
|
||||
Id tex2_id{};
|
||||
Id tex_cube_id{};
|
||||
Id texture_buffer_lut_lf_id{};
|
||||
Id texture_buffer_lut_rg_id{};
|
||||
Id texture_buffer_lut_rgba_id{};
|
||||
|
Loading…
x
Reference in New Issue
Block a user