From 2ec7f37d693ccfa0b00e9ad30f6b111bc26e2946 Mon Sep 17 00:00:00 2001 From: GPUCode Date: Sat, 30 Apr 2022 15:50:38 +0300 Subject: [PATCH] renderer_vulkan: Add vulkan swapchain class * This commit implements the Vulkan swapchain required for presenting images to the screen. Currently it's not complete as the API will be finalized in the next subsequent commits. --- src/video_core/CMakeLists.txt | 2 - src/video_core/renderer_vulkan/vk_instance.h | 6 + .../renderer_vulkan/vk_pipeline_manager.cpp | 668 ------------------ .../renderer_vulkan/vk_pipeline_manager.h | 131 ---- .../renderer_vulkan/vk_swapchain.cpp | 392 ++++------ src/video_core/renderer_vulkan/vk_swapchain.h | 39 +- src/video_core/renderer_vulkan/vk_texture.h | 4 +- 7 files changed, 157 insertions(+), 1085 deletions(-) delete mode 100644 src/video_core/renderer_vulkan/vk_pipeline_manager.cpp delete mode 100644 src/video_core/renderer_vulkan/vk_pipeline_manager.h diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 77516e72f..e3f905847 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -83,8 +83,6 @@ add_library(video_core STATIC renderer_vulkan/vk_rasterizer.h renderer_vulkan/vk_pipeline.cpp renderer_vulkan/vk_pipeline.h - renderer_vulkan/vk_pipeline_manager.cpp - renderer_vulkan/vk_pipeline_manager.h renderer_vulkan/vk_shader_state.h renderer_vulkan/vk_state.cpp renderer_vulkan/vk_state.h diff --git a/src/video_core/renderer_vulkan/vk_instance.h b/src/video_core/renderer_vulkan/vk_instance.h index 6fc3711ea..c868279ff 100644 --- a/src/video_core/renderer_vulkan/vk_instance.h +++ b/src/video_core/renderer_vulkan/vk_instance.h @@ -43,6 +43,12 @@ public: vk::Device& GetDevice() { return device.get(); } vk::PhysicalDevice& GetPhysicalDevice() { return physical_device; } + /// Retrieve queue information + u32 GetGraphicsQueueFamilyIndex() const { return graphics_queue_family_index; } + u32 GetPresentQueueFamilyIndex() const { return present_queue_family_index; } + vk::Queue GetGraphicsQueue() { return graphics_queue; } + vk::Queue GetPresentQueue() { return present_queue; } + /// Feature support bool SupportsAnisotropicFiltering() const; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_manager.cpp b/src/video_core/renderer_vulkan/vk_pipeline_manager.cpp deleted file mode 100644 index a6bdd5e78..000000000 --- a/src/video_core/renderer_vulkan/vk_pipeline_manager.cpp +++ /dev/null @@ -1,668 +0,0 @@ -// Copyright 2022 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "core/core.h" -#include "video_core/video_core.h" -#include "core/frontend/scope_acquire_context.h" -#include "video_core/renderer_vulkan/vk_pipeline_manager.h" -#include -#include -#include -#include -#include - -namespace Vulkan { - -static u64 GetUniqueIdentifier(const Pica::Regs& regs, const ProgramCode& code) { - std::size_t hash = 0; - u64 regs_uid = Common::ComputeHash64(regs.reg_array.data(), Pica::Regs::NUM_REGS * sizeof(u32)); - boost::hash_combine(hash, regs_uid); - if (code.size() > 0) { - u64 code_uid = Common::ComputeHash64(code.data(), code.size() * sizeof(u32)); - boost::hash_combine(hash, code_uid); - } - return static_cast(hash); -} - -static OGLProgram GeneratePrecompiledProgram(const ShaderDiskCacheDump& dump, - const std::set& supported_formats, - bool separable) { - - if (supported_formats.find(dump.binary_format) == supported_formats.end()) { - LOG_INFO(Render_OpenGL, "Precompiled cache entry with unsupported format - removing"); - return {}; - } - - auto shader = OGLProgram(); - shader.handle = glCreateProgram(); - if (separable) { - glProgramParameteri(shader.handle, GL_PROGRAM_SEPARABLE, GL_TRUE); - } - glProgramBinary(shader.handle, dump.binary_format, dump.binary.data(), - static_cast(dump.binary.size())); - - GLint link_status{}; - glGetProgramiv(shader.handle, GL_LINK_STATUS, &link_status); - if (link_status == GL_FALSE) { - LOG_INFO(Render_OpenGL, "Precompiled cache rejected by the driver - removing"); - return {}; - } - - return shader; -} - -static std::set GetSupportedFormats() { - std::set supported_formats; - - GLint num_formats{}; - glGetIntegerv(GL_NUM_PROGRAM_BINARY_FORMATS, &num_formats); - - std::vector formats(num_formats); - glGetIntegerv(GL_PROGRAM_BINARY_FORMATS, formats.data()); - - for (const GLint format : formats) - supported_formats.insert(static_cast(format)); - return supported_formats; -} - -static std::tuple BuildVSConfigFromRaw( - const ShaderDiskCacheRaw& raw) { - Pica::Shader::ProgramCode program_code{}; - Pica::Shader::SwizzleData swizzle_data{}; - std::copy_n(raw.GetProgramCode().begin(), Pica::Shader::MAX_PROGRAM_CODE_LENGTH, - program_code.begin()); - std::copy_n(raw.GetProgramCode().begin() + Pica::Shader::MAX_PROGRAM_CODE_LENGTH, - Pica::Shader::MAX_SWIZZLE_DATA_LENGTH, swizzle_data.begin()); - Pica::Shader::ShaderSetup setup; - setup.program_code = program_code; - setup.swizzle_data = swizzle_data; - return {PicaVSConfig{raw.GetRawShaderConfig().vs, setup}, setup}; -} - -static void SetShaderUniformBlockBinding(GLuint shader, const char* name, UniformBindings binding, - std::size_t expected_size) { - const GLuint ub_index = glGetUniformBlockIndex(shader, name); - if (ub_index == GL_INVALID_INDEX) { - return; - } - GLint ub_size = 0; - glGetActiveUniformBlockiv(shader, ub_index, GL_UNIFORM_BLOCK_DATA_SIZE, &ub_size); - ASSERT_MSG(ub_size == expected_size, "Uniform block size did not match! Got {}, expected {}", - static_cast(ub_size), expected_size); - glUniformBlockBinding(shader, ub_index, static_cast(binding)); -} - -static void SetShaderUniformBlockBindings(GLuint shader) { - SetShaderUniformBlockBinding(shader, "shader_data", UniformBindings::Common, - sizeof(UniformData)); - SetShaderUniformBlockBinding(shader, "vs_config", UniformBindings::VS, sizeof(VSUniformData)); -} - -static void SetShaderSamplerBinding(GLuint shader, const char* name, - TextureUnits::TextureUnit binding) { - GLint uniform_tex = glGetUniformLocation(shader, name); - if (uniform_tex != -1) { - glUniform1i(uniform_tex, binding.id); - } -} - -static void SetShaderImageBinding(GLuint shader, const char* name, GLuint binding) { - GLint uniform_tex = glGetUniformLocation(shader, name); - if (uniform_tex != -1) { - glUniform1i(uniform_tex, static_cast(binding)); - } -} - -static void SetShaderSamplerBindings(GLuint shader) { - OpenGLState cur_state = OpenGLState::GetCurState(); - GLuint old_program = std::exchange(cur_state.draw.shader_program, shader); - cur_state.Apply(); - - // Set the texture samplers to correspond to different texture units - SetShaderSamplerBinding(shader, "tex0", TextureUnits::PicaTexture(0)); - SetShaderSamplerBinding(shader, "tex1", TextureUnits::PicaTexture(1)); - SetShaderSamplerBinding(shader, "tex2", TextureUnits::PicaTexture(2)); - SetShaderSamplerBinding(shader, "tex_cube", TextureUnits::TextureCube); - - // Set the texture samplers to correspond to different lookup table texture units - SetShaderSamplerBinding(shader, "texture_buffer_lut_lf", TextureUnits::TextureBufferLUT_LF); - SetShaderSamplerBinding(shader, "texture_buffer_lut_rg", TextureUnits::TextureBufferLUT_RG); - SetShaderSamplerBinding(shader, "texture_buffer_lut_rgba", TextureUnits::TextureBufferLUT_RGBA); - - SetShaderImageBinding(shader, "shadow_buffer", ImageUnits::ShadowBuffer); - SetShaderImageBinding(shader, "shadow_texture_px", ImageUnits::ShadowTexturePX); - SetShaderImageBinding(shader, "shadow_texture_nx", ImageUnits::ShadowTextureNX); - SetShaderImageBinding(shader, "shadow_texture_py", ImageUnits::ShadowTexturePY); - SetShaderImageBinding(shader, "shadow_texture_ny", ImageUnits::ShadowTextureNY); - SetShaderImageBinding(shader, "shadow_texture_pz", ImageUnits::ShadowTexturePZ); - SetShaderImageBinding(shader, "shadow_texture_nz", ImageUnits::ShadowTextureNZ); - - cur_state.draw.shader_program = old_program; - cur_state.Apply(); -} - -void PicaUniformsData::SetFromRegs(const Pica::ShaderRegs& regs, - const Pica::Shader::ShaderSetup& setup) { - std::transform(std::begin(setup.uniforms.b), std::end(setup.uniforms.b), std::begin(bools), - [](bool value) -> BoolAligned { return {value ? GL_TRUE : GL_FALSE}; }); - std::transform(std::begin(regs.int_uniforms), std::end(regs.int_uniforms), std::begin(i), - [](const auto& value) -> GLuvec4 { - return {value.x.Value(), value.y.Value(), value.z.Value(), value.w.Value()}; - }); - std::transform(std::begin(setup.uniforms.f), std::end(setup.uniforms.f), std::begin(f), - [](const auto& value) -> GLvec4 { - return {value.x.ToFloat32(), value.y.ToFloat32(), value.z.ToFloat32(), - value.w.ToFloat32()}; - }); -} - -template -class ShaderCache { -public: - explicit ShaderCache(bool separable) : separable(separable) {} - std::tuple> Get( - const KeyConfigType& config) { - auto [iter, new_shader] = shaders.emplace(config, OGLShaderStage{separable}); - OGLShaderStage& cached_shader = iter->second; - std::optional result{}; - if (new_shader) { - result = CodeGenerator(config, separable); - cached_shader.Create(result->code.c_str(), ShaderType); - } - return {cached_shader.GetHandle(), std::move(result)}; - } - - void Inject(const KeyConfigType& key, OGLProgram&& program) { - OGLShaderStage stage{separable}; - stage.Inject(std::move(program)); - shaders.emplace(key, std::move(stage)); - } - - void Inject(const KeyConfigType& key, OGLShaderStage&& stage) { - shaders.emplace(key, std::move(stage)); - } - -private: - bool separable; - std::unordered_map shaders; -}; - -// This is a cache designed for shaders translated from PICA shaders. The first cache matches the -// config structure like a normal cache does. On cache miss, the second cache matches the generated -// GLSL code. The configuration is like this because there might be leftover code in the PICA shader -// program buffer from the previous shader, which is hashed into the config, resulting several -// different config values from the same shader program. -template (*CodeGenerator)( - const Pica::Shader::ShaderSetup&, const KeyConfigType&, bool), - GLenum ShaderType> -class ShaderDoubleCache { -public: - explicit ShaderDoubleCache(bool separable) : separable(separable) {} - std::tuple> Get( - const KeyConfigType& key, const Pica::Shader::ShaderSetup& setup) { - std::optional result{}; - auto map_it = shader_map.find(key); - if (map_it == shader_map.end()) { - auto program_opt = CodeGenerator(setup, key, separable); - if (!program_opt) { - shader_map[key] = nullptr; - return {0, std::nullopt}; - } - - std::string& program = program_opt->code; - auto [iter, new_shader] = shader_cache.emplace(program, OGLShaderStage{separable}); - OGLShaderStage& cached_shader = iter->second; - if (new_shader) { - result.emplace(); - result->code = program; - cached_shader.Create(program.c_str(), ShaderType); - } - shader_map[key] = &cached_shader; - return {cached_shader.GetHandle(), std::move(result)}; - } - - if (map_it->second == nullptr) { - return {0, std::nullopt}; - } - - return {map_it->second->GetHandle(), std::nullopt}; - } - - void Inject(const KeyConfigType& key, std::string decomp, OGLProgram&& program) { - OGLShaderStage stage{separable}; - stage.Inject(std::move(program)); - const auto iter = shader_cache.emplace(std::move(decomp), std::move(stage)).first; - OGLShaderStage& cached_shader = iter->second; - shader_map.insert_or_assign(key, &cached_shader); - } - - void Inject(const KeyConfigType& key, std::string decomp, OGLShaderStage&& stage) { - const auto iter = shader_cache.emplace(std::move(decomp), std::move(stage)).first; - OGLShaderStage& cached_shader = iter->second; - shader_map.insert_or_assign(key, &cached_shader); - } - -private: - bool separable; - std::unordered_map shader_map; - std::unordered_map shader_cache; -}; - -using ProgrammableVertexShaders = - ShaderDoubleCache; - -using FixedGeometryShaders = - ShaderCache; - -using FragmentShaders = ShaderCache; - -class ShaderProgramManager::Impl { -public: - explicit Impl(bool separable, bool is_amd) - : is_amd(is_amd), separable(separable), programmable_vertex_shaders(separable), - trivial_vertex_shader(separable), fixed_geometry_shaders(separable), - fragment_shaders(separable), disk_cache(separable) { - if (separable) - pipeline.Create(); - } - - struct ShaderTuple { - GLuint vs = 0; - GLuint gs = 0; - GLuint fs = 0; - - std::size_t vs_hash = 0; - std::size_t gs_hash = 0; - std::size_t fs_hash = 0; - - bool operator==(const ShaderTuple& rhs) const { - return std::tie(vs, gs, fs) == std::tie(rhs.vs, rhs.gs, rhs.fs); - } - - bool operator!=(const ShaderTuple& rhs) const { - return std::tie(vs, gs, fs) != std::tie(rhs.vs, rhs.gs, rhs.fs); - } - - std::size_t GetConfigHash() const { - std::size_t hash = 0; - boost::hash_combine(hash, vs_hash); - boost::hash_combine(hash, gs_hash); - boost::hash_combine(hash, fs_hash); - return hash; - } - }; - - bool is_amd; - bool separable; - - ShaderTuple current; - - ProgrammableVertexShaders programmable_vertex_shaders; - TrivialVertexShader trivial_vertex_shader; - - FixedGeometryShaders fixed_geometry_shaders; - - FragmentShaders fragment_shaders; - std::unordered_map program_cache; - OGLPipeline pipeline; - ShaderDiskCache disk_cache; -}; - -ShaderProgramManager::ShaderProgramManager(Frontend::EmuWindow& emu_window_, bool separable, - bool is_amd) - : impl(std::make_unique(separable, is_amd)), emu_window{emu_window_} {} - -ShaderProgramManager::~ShaderProgramManager() = default; - -bool ShaderProgramManager::UseProgrammableVertexShader(const Pica::Regs& regs, - Pica::Shader::ShaderSetup& setup) { - PicaVSConfig config{regs.vs, setup}; - auto [handle, result] = impl->programmable_vertex_shaders.Get(config, setup); - if (handle == 0) - return false; - impl->current.vs = handle; - impl->current.vs_hash = config.Hash(); - - // Save VS to the disk cache if its a new shader - if (result) { - auto& disk_cache = impl->disk_cache; - ProgramCode program_code{setup.program_code.begin(), setup.program_code.end()}; - program_code.insert(program_code.end(), setup.swizzle_data.begin(), - setup.swizzle_data.end()); - const u64 unique_identifier = GetUniqueIdentifier(regs, program_code); - const ShaderDiskCacheRaw raw{unique_identifier, ProgramType::VS, regs, - std::move(program_code)}; - disk_cache.SaveRaw(raw); - disk_cache.SaveDecompiled(unique_identifier, *result, VideoCore::g_hw_shader_accurate_mul); - } - return true; -} - -void ShaderProgramManager::UseTrivialVertexShader() { - impl->current.vs = impl->trivial_vertex_shader.Get(); - impl->current.vs_hash = 0; -} - -void ShaderProgramManager::UseFixedGeometryShader(const Pica::Regs& regs) { - PicaFixedGSConfig gs_config(regs); - auto [handle, _] = impl->fixed_geometry_shaders.Get(gs_config); - impl->current.gs = handle; - impl->current.gs_hash = gs_config.Hash(); -} - -void ShaderProgramManager::UseTrivialGeometryShader() { - impl->current.gs = 0; - impl->current.gs_hash = 0; -} - -void ShaderProgramManager::UseFragmentShader(const Pica::Regs& regs) { - PicaFSConfig config = PicaFSConfig::BuildFromRegs(regs); - auto [handle, result] = impl->fragment_shaders.Get(config); - impl->current.fs = handle; - impl->current.fs_hash = config.Hash(); - // Save FS to the disk cache if its a new shader - if (result) { - auto& disk_cache = impl->disk_cache; - u64 unique_identifier = GetUniqueIdentifier(regs, {}); - ShaderDiskCacheRaw raw{unique_identifier, ProgramType::FS, regs, {}}; - disk_cache.SaveRaw(raw); - disk_cache.SaveDecompiled(unique_identifier, *result, false); - } -} - -void ShaderProgramManager::ApplyTo(OpenGLState& state) { - if (impl->separable) { - if (impl->is_amd) { - // Without this reseting, AMD sometimes freezes when one stage is changed but not - // for the others. On the other hand, including this reset seems to introduce memory - // leak in Intel Graphics. - glUseProgramStages( - impl->pipeline.handle, - GL_VERTEX_SHADER_BIT | GL_GEOMETRY_SHADER_BIT | GL_FRAGMENT_SHADER_BIT, 0); - } - - glUseProgramStages(impl->pipeline.handle, GL_VERTEX_SHADER_BIT, impl->current.vs); - glUseProgramStages(impl->pipeline.handle, GL_GEOMETRY_SHADER_BIT, impl->current.gs); - glUseProgramStages(impl->pipeline.handle, GL_FRAGMENT_SHADER_BIT, impl->current.fs); - state.draw.shader_program = 0; - state.draw.program_pipeline = impl->pipeline.handle; - } else { - const u64 unique_identifier = impl->current.GetConfigHash(); - OGLProgram& cached_program = impl->program_cache[unique_identifier]; - if (cached_program.handle == 0) { - cached_program.Create(false, {impl->current.vs, impl->current.gs, impl->current.fs}); - auto& disk_cache = impl->disk_cache; - disk_cache.SaveDumpToFile(unique_identifier, cached_program.handle, - VideoCore::g_hw_shader_accurate_mul); - - SetShaderUniformBlockBindings(cached_program.handle); - SetShaderSamplerBindings(cached_program.handle); - } - state.draw.shader_program = cached_program.handle; - } -} - -void ShaderProgramManager::LoadDiskCache(const std::atomic_bool& stop_loading, - const VideoCore::DiskResourceLoadCallback& callback) { - if (!GLAD_GL_ARB_get_program_binary && !GLES) { - LOG_ERROR(Render_OpenGL, - "Cannot load disk cache as ARB_get_program_binary is not supported!"); - return; - } - - auto& disk_cache = impl->disk_cache; - const auto transferable = disk_cache.LoadTransferable(); - if (!transferable) { - return; - } - const auto& raws = *transferable; - - // Load uncompressed precompiled file for non-separable shaders. - // Precompiled file for separable shaders is compressed. - auto [decompiled, dumps] = disk_cache.LoadPrecompiled(impl->separable); - - if (stop_loading) { - return; - } - - std::set supported_formats = GetSupportedFormats(); - - // Track if precompiled cache was altered during loading to know if we have to serialize the - // virtual precompiled cache file back to the hard drive - bool precompiled_cache_altered = false; - - std::mutex mutex; - std::atomic_bool compilation_failed = false; - if (callback) { - callback(VideoCore::LoadCallbackStage::Decompile, 0, raws.size()); - } - std::vector load_raws_index; - // Loads both decompiled and precompiled shaders from the cache. If either one is missing for - const auto LoadPrecompiledShader = [&](std::size_t begin, std::size_t end, - const std::vector& raw_cache, - const ShaderDecompiledMap& decompiled_map, - const ShaderDumpsMap& dump_map) { - for (std::size_t i = begin; i < end; ++i) { - if (stop_loading || compilation_failed) { - return; - } - const auto& raw{raw_cache[i]}; - const u64 unique_identifier{raw.GetUniqueIdentifier()}; - - const u64 calculated_hash = - GetUniqueIdentifier(raw.GetRawShaderConfig(), raw.GetProgramCode()); - if (unique_identifier != calculated_hash) { - LOG_ERROR(Render_OpenGL, - "Invalid hash in entry={:016x} (obtained hash={:016x}) - removing " - "shader cache", - raw.GetUniqueIdentifier(), calculated_hash); - disk_cache.InvalidateAll(); - return; - } - - const auto dump{dump_map.find(unique_identifier)}; - const auto decomp{decompiled_map.find(unique_identifier)}; - - OGLProgram shader; - - if (dump != dump_map.end() && decomp != decompiled_map.end()) { - // Only load the vertex shader if its sanitize_mul setting matches - if (raw.GetProgramType() == ProgramType::VS && - decomp->second.sanitize_mul != VideoCore::g_hw_shader_accurate_mul) { - continue; - } - - // If the shader is dumped, attempt to load it - shader = - GeneratePrecompiledProgram(dump->second, supported_formats, impl->separable); - if (shader.handle == 0) { - // If any shader failed, stop trying to compile, delete the cache, and start - // loading from raws - compilation_failed = true; - return; - } - // we have both the binary shader and the decompiled, so inject it into the - // cache - if (raw.GetProgramType() == ProgramType::VS) { - auto [conf, setup] = BuildVSConfigFromRaw(raw); - std::scoped_lock lock(mutex); - impl->programmable_vertex_shaders.Inject(conf, decomp->second.result.code, - std::move(shader)); - } else if (raw.GetProgramType() == ProgramType::FS) { - PicaFSConfig conf = PicaFSConfig::BuildFromRegs(raw.GetRawShaderConfig()); - std::scoped_lock lock(mutex); - impl->fragment_shaders.Inject(conf, std::move(shader)); - } else { - // Unsupported shader type got stored somehow so nuke the cache - - LOG_CRITICAL(Frontend, "failed to load raw ProgramType {}", - raw.GetProgramType()); - compilation_failed = true; - return; - } - } else { - // Since precompiled didn't have the dump, we'll load them in the next phase - std::scoped_lock lock(mutex); - load_raws_index.push_back(i); - } - if (callback) { - callback(VideoCore::LoadCallbackStage::Decompile, i, raw_cache.size()); - } - } - }; - - const auto LoadPrecompiledProgram = [&](const ShaderDecompiledMap& decompiled_map, - const ShaderDumpsMap& dump_map) { - std::size_t i{0}; - for (const auto& dump : dump_map) { - if (stop_loading) { - break; - } - const u64 unique_identifier{dump.first}; - const auto decomp{decompiled_map.find(unique_identifier)}; - - // Only load the program if its sanitize_mul setting matches - if (decomp->second.sanitize_mul != VideoCore::g_hw_shader_accurate_mul) { - continue; - } - - // If the shader program is dumped, attempt to load it - OGLProgram shader = - GeneratePrecompiledProgram(dump.second, supported_formats, impl->separable); - if (shader.handle != 0) { - SetShaderUniformBlockBindings(shader.handle); - SetShaderSamplerBindings(shader.handle); - impl->program_cache.emplace(unique_identifier, std::move(shader)); - } else { - LOG_ERROR(Frontend, "Failed to link Precompiled program!"); - compilation_failed = true; - break; - } - if (callback) { - callback(VideoCore::LoadCallbackStage::Decompile, ++i, dump_map.size()); - } - } - }; - - if (impl->separable) { - LoadPrecompiledShader(0, raws.size(), raws, decompiled, dumps); - } else { - LoadPrecompiledProgram(decompiled, dumps); - } - - bool load_all_raws = false; - if (compilation_failed) { - // Invalidate the precompiled cache if a shader dumped shader was rejected - impl->program_cache.clear(); - disk_cache.InvalidatePrecompiled(); - dumps.clear(); - precompiled_cache_altered = true; - load_all_raws = true; - } - // TODO(SachinV): Skip loading raws until we implement a proper way to link non-seperable - // shaders. - if (!impl->separable) { - return; - } - - const std::size_t load_raws_size = load_all_raws ? raws.size() : load_raws_index.size(); - - if (callback) { - callback(VideoCore::LoadCallbackStage::Build, 0, load_raws_size); - } - - compilation_failed = false; - - std::size_t built_shaders = 0; // It doesn't have be atomic since it's used behind a mutex - const auto LoadRawSepareble = [&](Frontend::GraphicsContext* context, std::size_t begin, - std::size_t end) { - Frontend::ScopeAcquireContext scope(*context); - for (std::size_t i = begin; i < end; ++i) { - if (stop_loading || compilation_failed) { - return; - } - - const std::size_t raws_index = load_all_raws ? i : load_raws_index[i]; - const auto& raw{raws[raws_index]}; - const u64 unique_identifier{raw.GetUniqueIdentifier()}; - - bool sanitize_mul = false; - GLuint handle{0}; - std::optional result; - // Otherwise decompile and build the shader at boot and save the result to the - // precompiled file - if (raw.GetProgramType() == ProgramType::VS) { - auto [conf, setup] = BuildVSConfigFromRaw(raw); - result = GenerateVertexShader(setup, conf, impl->separable); - OGLShaderStage stage{impl->separable}; - stage.Create(result->code.c_str(), GL_VERTEX_SHADER); - handle = stage.GetHandle(); - sanitize_mul = conf.state.sanitize_mul; - std::scoped_lock lock(mutex); - impl->programmable_vertex_shaders.Inject(conf, result->code, std::move(stage)); - } else if (raw.GetProgramType() == ProgramType::FS) { - PicaFSConfig conf = PicaFSConfig::BuildFromRegs(raw.GetRawShaderConfig()); - result = GenerateFragmentShader(conf, impl->separable); - OGLShaderStage stage{impl->separable}; - stage.Create(result->code.c_str(), GL_FRAGMENT_SHADER); - handle = stage.GetHandle(); - std::scoped_lock lock(mutex); - impl->fragment_shaders.Inject(conf, std::move(stage)); - } else { - // Unsupported shader type got stored somehow so nuke the cache - LOG_ERROR(Frontend, "failed to load raw ProgramType {}", raw.GetProgramType()); - compilation_failed = true; - return; - } - if (handle == 0) { - LOG_ERROR(Frontend, "compilation from raw failed {:x} {:x}", - raw.GetProgramCode().at(0), raw.GetProgramCode().at(1)); - compilation_failed = true; - return; - } - - std::scoped_lock lock(mutex); - // If this is a new separable shader, add it the precompiled cache - if (result) { - disk_cache.SaveDecompiled(unique_identifier, *result, sanitize_mul); - disk_cache.SaveDump(unique_identifier, handle); - precompiled_cache_altered = true; - } - - if (callback) { - callback(VideoCore::LoadCallbackStage::Build, ++built_shaders, load_raws_size); - } - } - }; - - const std::size_t num_workers{std::max(1U, std::thread::hardware_concurrency())}; - const std::size_t bucket_size{load_raws_size / num_workers}; - std::vector> contexts(num_workers); - std::vector threads(num_workers); - for (std::size_t i = 0; i < num_workers; ++i) { - const bool is_last_worker = i + 1 == num_workers; - const std::size_t start{bucket_size * i}; - const std::size_t end{is_last_worker ? load_raws_size : start + bucket_size}; - - // On some platforms the shared context has to be created from the GUI thread - contexts[i] = emu_window.CreateSharedContext(); - threads[i] = std::thread(LoadRawSepareble, contexts[i].get(), start, end); - } - for (auto& thread : threads) { - thread.join(); - } - - if (compilation_failed) { - disk_cache.InvalidateAll(); - } - - if (precompiled_cache_altered) { - disk_cache.SaveVirtualPrecompiledFile(); - } -} - -} // namespace OpenGL diff --git a/src/video_core/renderer_vulkan/vk_pipeline_manager.h b/src/video_core/renderer_vulkan/vk_pipeline_manager.h deleted file mode 100644 index 8b00b2c94..000000000 --- a/src/video_core/renderer_vulkan/vk_pipeline_manager.h +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright 2022 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include -#include -#include "video_core/rasterizer_interface.h" -#include "video_core/regs_lighting.h" -#include "video_core/renderer_vulkan/pica_to_vulkan.h" -#include "video_core/renderer_vulkan/vk_shader_state.h" -#include "video_core/renderer_vulkan/vk_texture.h" - -namespace Core { -class System; -} - -namespace Vulkan { - -enum class UniformBindings : GLuint { Common, VS, GS }; - -struct LightSrc { - alignas(16) glm::vec3 specular_0; - alignas(16) glm::vec3 specular_1; - alignas(16) glm::vec3 diffuse; - alignas(16) glm::vec3 ambient; - alignas(16) glm::vec3 position; - alignas(16) glm::vec3 spot_direction; // negated - float dist_atten_bias; - float dist_atten_scale; -}; - -/// Uniform structure for the Uniform Buffer Object, all vectors must be 16-byte aligned -// NOTE: Always keep a vec4 at the end. The GL spec is not clear wether the alignment at -// the end of a uniform block is included in UNIFORM_BLOCK_DATA_SIZE or not. -// Not following that rule will cause problems on some AMD drivers. -struct UniformData { - int framebuffer_scale; - int alphatest_ref; - float depth_scale; - float depth_offset; - float shadow_bias_constant; - float shadow_bias_linear; - int scissor_x1; - int scissor_y1; - int scissor_x2; - int scissor_y2; - int fog_lut_offset; - int proctex_noise_lut_offset; - int proctex_color_map_offset; - int proctex_alpha_map_offset; - int proctex_lut_offset; - int proctex_diff_lut_offset; - float proctex_bias; - int shadow_texture_bias; - alignas(16) glm::ivec4 lighting_lut_offset[Pica::LightingRegs::NumLightingSampler / 4]; - alignas(16) glm::vec3 fog_color; - alignas(8) glm::vec2 proctex_noise_f; - alignas(8) glm::vec2 proctex_noise_a; - alignas(8) glm::vec2 proctex_noise_p; - alignas(16) glm::vec3 lighting_global_ambient; - LightSrc light_src[8]; - alignas(16) glm::vec4 const_color[6]; // A vec4 color for each of the six tev stages - alignas(16) glm::vec4 tev_combiner_buffer_color; - alignas(16) glm::vec4 clip_coef; -}; - -static_assert(sizeof(UniformData) == 0x4F0, "The size of the UniformData structure has changed, update the structure in the shader"); -static_assert(sizeof(UniformData) < 16384, "UniformData structure must be less than 16kb as per the OpenGL spec"); - -/// Uniform struct for the Uniform Buffer Object that contains PICA vertex/geometry shader uniforms. -// NOTE: the same rule from UniformData also applies here. -struct PicaUniformsData { - void SetFromRegs(const Pica::ShaderRegs& regs, const Pica::Shader::ShaderSetup& setup); - - struct BoolAligned { - alignas(16) int b; - }; - - std::array bools; - alignas(16) std::array i; - alignas(16) std::array f; -}; - -struct VSUniformData { - PicaUniformsData uniforms; -}; - -static_assert(sizeof(VSUniformData) == 1856, "The size of the VSUniformData structure has changed, update the structure in the shader"); -static_assert(sizeof(VSUniformData) < 16384, "VSUniformData structure must be less than 16kb as per the Vulkan spec"); - -using Resource = std::variant; - -/// Includes all required information to build a Vulkan pipeline object -class VKPipelineInfo : private NonCopyable { - VKPipelineInfo() = default; - ~VKPipelineInfo() = default; - - /// Assign a shader module to a specific stage - void AddShaderModule(const vk::ShaderModule& module, vk::ShaderStageFlagBits stage); - - /// Add a texture or a buffer to the target descriptor set - void AddResource(const Resource& resource, vk::DescriptorType type, vk::ShaderStageFlags stages, int set = 0); - -private: - using ResourceInfo = std::pair, vk::DescriptorSetLayoutBinding>; - - std::unordered_map> descriptor_sets; - std::vector shader_stages; -}; - -/// A class that manages the storage and management of Vulkan pipeline objects. -class PipelineManager { -public: - PipelineManager(Frontend::EmuWindow& emu_window); - ~PipelineManager(); - - /// Retrieves the Vulkan pipeline that maps to the current PICA state. - /// If not present, it is compiled and cached - vk::Pipeline GetPipeline(const Pica::Regs& config, Pica::Shader::ShaderSetup& setup); - -private: - std::unordered_map pipelines; - vk::UniquePipelineCache pipeline_cache; - - Frontend::EmuWindow& emu_window; -}; -} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp index 4292ac2ce..4ed6759ee 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.cpp +++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp @@ -13,16 +13,102 @@ namespace Vulkan { -struct SwapchainDetails { - vk::SurfaceFormatKHR format; - vk::PresentModeKHR present_mode; - vk::Extent2D extent; - vk::SurfaceTransformFlagBitsKHR transform; - u32 image_count; -}; +VKSwapChain::VKSwapChain(vk::SurfaceKHR surface_) : surface(surface_) { -SwapchainDetails PopulateSwapchainDetails(vk::SurfaceKHR surface, u32 width, u32 height) { - SwapchainDetails details; +} + +bool VKSwapChain::Create(u32 width, u32 height, bool vsync_enabled) { + is_outdated = false; + is_suboptimal = false; + + // Fetch information about the provided surface + PopulateSwapchainDetails(surface, width, height); + + // Now we can actually create the swapchain + vk::SwapchainCreateInfoKHR swapchain_info + ( + {}, + surface, + details.image_count, + details.format.format, details.format.colorSpace, + details.extent, 1, + vk::ImageUsageFlagBits::eColorAttachment, + vk::SharingMode::eExclusive, + 0, nullptr, + details.transform, + vk::CompositeAlphaFlagBitsKHR::eOpaque, + details.present_mode, + VK_TRUE, + swapchain.get() + ); + + std::array indices = { + g_vk_instace->GetGraphicsQueueFamilyIndex(), + g_vk_instace->GetPresentQueueFamilyIndex(), + }; + + // For dedicated present queues, select concurrent sharing mode + if (indices[0] != indices[1]) { + swapchain_info.setImageSharingMode(vk::SharingMode::eConcurrent); + swapchain_info.setQueueFamilyIndices(indices); + } + + auto new_swapchain = g_vk_instace->GetDevice().createSwapchainKHRUnique(swapchain_info); + + // If an old swapchain exists, destroy it and move the new one to its place. + // Synchronization is the responsibility of the caller, not us + if (!!swapchain) { + swapchain_images.clear(); + swapchain.swap(new_swapchain); + } + + return true; +} + +void VKSwapChain::AcquireNextImage(vk::Semaphore present_semaphore) { + const auto result = g_vk_instace->GetDevice().acquireNextImageKHR(*swapchain, + std::numeric_limits::max(), present_semaphore, + VK_NULL_HANDLE, &image_index); + + switch (result) { + case vk::Result::eSuccess: + break; + case vk::Result::eSuboptimalKHR: + is_suboptimal = true; + break; + case vk::Result::eErrorOutOfDateKHR: + is_outdated = true; + break; + default: + LOG_ERROR(Render_Vulkan, "acquireNextImageKHR returned unknown result"); + break; + } +} + +void VKSwapChain::Present(vk::Semaphore render_semaphore) { + const auto present_queue = g_vk_instace->GetPresentQueue(); + + vk::PresentInfoKHR present_info(render_semaphore, swapchain.get(), image_index); + vk::Result result = present_queue.presentKHR(present_info); + + switch (result) { + case vk::Result::eSuccess: + break; + case vk::Result::eSuboptimalKHR: + LOG_DEBUG(Render_Vulkan, "Suboptimal swapchain"); + break; + case vk::Result::eErrorOutOfDateKHR: + is_outdated = true; + break; + default: + LOG_CRITICAL(Render_Vulkan, "Swapchain presentation failed"); + break; + } + + frame_index = (frame_index + 1) % swapchain_images.size(); +} + +void VKSwapChain::PopulateSwapchainDetails(vk::SurfaceKHR surface, u32 width, u32 height) { auto& gpu = g_vk_instace->GetPhysicalDevice(); // Choose surface format @@ -52,7 +138,7 @@ SwapchainDetails PopulateSwapchainDetails(vk::SurfaceKHR surface, u32 width, u32 return it != modes.end(); }; - // FIFO is guaranteed by the standard to be available + // FIFO is guaranteed by the Vulkan standard to be available details.present_mode = vk::PresentModeKHR::eFifo; // Prefer Mailbox if present for lowest latency @@ -60,7 +146,7 @@ SwapchainDetails PopulateSwapchainDetails(vk::SurfaceKHR surface, u32 width, u32 details.present_mode = vk::PresentModeKHR::eMailbox; } - // Query surface capabilities + // Query surface extent auto capabilities = gpu.getSurfaceCapabilitiesKHR(surface); details.extent = capabilities.currentExtent; @@ -82,267 +168,39 @@ SwapchainDetails PopulateSwapchainDetails(vk::SurfaceKHR surface, u32 width, u32 if (!(capabilities.supportedTransforms & details.transform)) { details.transform = capabilities.currentTransform; } - - return details; } -VKSwapchain::VKSwapchain(vk::SurfaceKHR surface_) : surface(surface_) { +void VKSwapChain::SetupImages() { + // Get the swap chain images + auto& device = g_vk_instace->GetDevice(); + auto images = device.getSwapchainImagesKHR(swapchain.get()); -} - -void VKSwapchain::Create(u32 width, u32 height, bool vsync_enabled) { - is_outdated = false; - is_suboptimal = false; - - const auto gpu = g_vk_instace->GetPhysicalDevice(); - auto details = PopulateSwapchainDetails(surface, width, height); - - // Store the old/current swap chain when recreating for resize - vk::SwapchainKHR old_swapchain = swapchain.get(); - - // Now we can actually create the swap chain - vk::SwapchainCreateInfoKHR swap_chain_info - ( - {}, - surface, - details.image_count, - details.format.format, details.format.colorSpace, - details.extent, 1, - vk::ImageUsageFlagBits::eColorAttachment, - vk::SharingMode::eExclusive, - 0, nullptr, - details.transform, - vk::CompositeAlphaFlagBitsKHR::eOpaque, - details.present_mode, - VK_TRUE, - old_swapchain - ); - - std::array indices = {{ - g_vulkan_context->GetGraphicsQueueFamilyIndex(), - g_vulkan_context->GetPresentQueueFamilyIndex(), - }}; - if (g_vulkan_context->GetGraphicsQueueFamilyIndex() != - g_vulkan_context->GetPresentQueueFamilyIndex()) + // Create the swapchain buffers containing the image and imageview + swapchain_images.resize(images.size()); + for (size_t i = 0; i < swapchain_images.size(); i++) { - swap_chain_info.imageSharingMode = VK_SHARING_MODE_CONCURRENT; - swap_chain_info.queueFamilyIndexCount = 2; - swap_chain_info.pQueueFamilyIndices = indices.data(); + swapchain_images[i].image = images[i]; + + // Create swapchain image view + vk::ImageViewCreateInfo color_attachment_view + ( + {}, + images[i], + vk::ImageViewType::e2D, + details.format.format, + {}, + { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } + ); + + swapchain_images[i].image_view = device.createImageViewUnique(color_attachment_view); + + // Create framebuffer for each swapchain image + VKFramebuffer::Info fb_info = { + .color = // TODO + }; + + swapchain_images[i].framebuffer.Create(fb_info); } - - #ifdef SUPPORTS_VULKAN_EXCLUSIVE_FULLSCREEN - if (m_fullscreen_supported) - { - VkSurfaceFullScreenExclusiveInfoEXT fullscreen_support = {}; - swap_chain_info.pNext = &fullscreen_support; - fullscreen_support.sType = VK_STRUCTURE_TYPE_SURFACE_FULL_SCREEN_EXCLUSIVE_INFO_EXT; - fullscreen_support.fullScreenExclusive = VK_FULL_SCREEN_EXCLUSIVE_APPLICATION_CONTROLLED_EXT; - - auto platform_info = g_vulkan_context->GetPlatformExclusiveFullscreenInfo(m_wsi); - fullscreen_support.pNext = &platform_info; - - res = vkCreateSwapchainKHR(g_vulkan_context->GetDevice(), &swap_chain_info, nullptr, - &m_swap_chain); - if (res != VK_SUCCESS) - { - // Try without exclusive fullscreen. - WARN_LOG_FMT(VIDEO, "Failed to create exclusive fullscreen swapchain, trying without."); - swap_chain_info.pNext = nullptr; - g_Config.backend_info.bSupportsExclusiveFullscreen = false; - g_ActiveConfig.backend_info.bSupportsExclusiveFullscreen = false; - m_fullscreen_supported = false; - } - } - #endif - - if (m_swap_chain == VK_NULL_HANDLE) - { - res = vkCreateSwapchainKHR(g_vulkan_context->GetDevice(), &swap_chain_info, nullptr, - &m_swap_chain); - } - if (res != VK_SUCCESS) - { - LOG_VULKAN_ERROR(res, "vkCreateSwapchainKHR failed: "); - return false; - } - - // Now destroy the old swap chain, since it's been recreated. - // We can do this immediately since all work should have been completed before calling resize. - if (old_swap_chain != VK_NULL_HANDLE) - vkDestroySwapchainKHR(g_vulkan_context->GetDevice(), old_swap_chain, nullptr); - - m_width = size.width; - m_height = size.height; - m_layers = image_layers; - return true; -} - -void VKSwapchain::AcquireNextImage() { - const auto result = g_vk_instace->GetDevice().acquireNextImageKHR(*swapchain, - std::numeric_limits::max(), *present_semaphores[frame_index], - VK_NULL_HANDLE, &image_index); - - switch (result) { - case vk::Result::eSuccess: - break; - case vk::Result::eSuboptimalKHR: - is_suboptimal = true; - break; - case vk::Result::eErrorOutOfDateKHR: - is_outdated = true; - break; - default: - LOG_ERROR(Render_Vulkan, "acquireNextImageKHR returned unknown result"); - break; - } -} - -void VKSwapchain::Present(vk::Semaphore render_semaphore) { - const auto present_queue{device.GetPresentQueue()}; - - - vk::PresentInfoKHR present_info( - - const VkPresentInfoKHR present_info{ - .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, - .pNext = nullptr, - .waitSemaphoreCount = render_semaphore ? 1U : 0U, - .pWaitSemaphores = &render_semaphore, - .swapchainCount = 1, - .pSwapchains = swapchain.address(), - .pImageIndices = &image_index, - .pResults = nullptr, - }; - switch (const VkResult result = present_queue.Present(present_info)) { - case VK_SUCCESS: - break; - case VK_SUBOPTIMAL_KHR: - LOG_DEBUG(Render_Vulkan, "Suboptimal swapchain"); - break; - case VK_ERROR_OUT_OF_DATE_KHR: - is_outdated = true; - break; - default: - LOG_CRITICAL(Render_Vulkan, "Failed to present with error {}", vk::ToString(result)); - break; - } - ++frame_index; - if (frame_index >= image_count) { - frame_index = 0; - } -} - -void VKSwapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities, u32 width, - u32 height, bool srgb) { - const auto physical_device{device.GetPhysical()}; - const auto formats{physical_device.GetSurfaceFormatsKHR(surface)}; - const auto present_modes{physical_device.GetSurfacePresentModesKHR(surface)}; - - const VkSurfaceFormatKHR surface_format{ChooseSwapSurfaceFormat(formats)}; - present_mode = ChooseSwapPresentMode(present_modes); - - u32 requested_image_count{capabilities.minImageCount + 1}; - if (capabilities.maxImageCount > 0 && requested_image_count > capabilities.maxImageCount) { - requested_image_count = capabilities.maxImageCount; - } - VkSwapchainCreateInfoKHR swapchain_ci{ - .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, - .pNext = nullptr, - .flags = 0, - .surface = surface, - .minImageCount = requested_image_count, - .imageFormat = surface_format.format, - .imageColorSpace = surface_format.colorSpace, - .imageExtent = {}, - .imageArrayLayers = 1, - .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, - .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE, - .queueFamilyIndexCount = 0, - .pQueueFamilyIndices = nullptr, - .preTransform = capabilities.currentTransform, - .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, - .presentMode = present_mode, - .clipped = VK_FALSE, - .oldSwapchain = nullptr, - }; - const u32 graphics_family{device.GetGraphicsFamily()}; - const u32 present_family{device.GetPresentFamily()}; - const std::array queue_indices{graphics_family, present_family}; - if (graphics_family != present_family) { - swapchain_ci.imageSharingMode = VK_SHARING_MODE_CONCURRENT; - swapchain_ci.queueFamilyIndexCount = static_cast(queue_indices.size()); - swapchain_ci.pQueueFamilyIndices = queue_indices.data(); - } - static constexpr std::array view_formats{VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_B8G8R8A8_SRGB}; - VkImageFormatListCreateInfo format_list{ - .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO_KHR, - .pNext = nullptr, - .viewFormatCount = static_cast(view_formats.size()), - .pViewFormats = view_formats.data(), - }; - if (device.IsKhrSwapchainMutableFormatEnabled()) { - format_list.pNext = std::exchange(swapchain_ci.pNext, &format_list); - swapchain_ci.flags |= VK_SWAPCHAIN_CREATE_MUTABLE_FORMAT_BIT_KHR; - } - // Request the size again to reduce the possibility of a TOCTOU race condition. - const auto updated_capabilities = physical_device.GetSurfaceCapabilitiesKHR(surface); - swapchain_ci.imageExtent = ChooseSwapExtent(updated_capabilities, width, height); - // Don't add code within this and the swapchain creation. - swapchain = device.GetLogical().CreateSwapchainKHR(swapchain_ci); - - extent = swapchain_ci.imageExtent; - current_srgb = srgb; - current_fps_unlocked = Settings::values.disable_fps_limit.GetValue(); - - images = swapchain.GetImages(); - image_count = static_cast(images.size()); - image_view_format = srgb ? VK_FORMAT_B8G8R8A8_SRGB : VK_FORMAT_B8G8R8A8_UNORM; -} - -void VKSwapchain::CreateImageViews() { - VkImageViewCreateInfo ci{ - .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .image = {}, - .viewType = VK_IMAGE_VIEW_TYPE_2D, - .format = image_view_format, - .components = - { - .r = VK_COMPONENT_SWIZZLE_IDENTITY, - .g = VK_COMPONENT_SWIZZLE_IDENTITY, - .b = VK_COMPONENT_SWIZZLE_IDENTITY, - .a = VK_COMPONENT_SWIZZLE_IDENTITY, - }, - .subresourceRange = - { - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1, - }, - }; - - image_views.resize(image_count); - for (std::size_t i = 0; i < image_count; i++) { - ci.image = images[i]; - image_views[i] = device.GetLogical().CreateImageView(ci); - } -} - -void VKSwapchain::Destroy() { - frame_index = 0; - present_semaphores.clear(); - framebuffers.clear(); - image_views.clear(); - swapchain.reset(); -} - -bool VKSwapchain::NeedsPresentModeUpdate() const { - // Mailbox present mode is the ideal for all scenarios. If it is not available, - // A different present mode is needed to support unlocked FPS above the monitor's refresh rate. - return present_mode != VK_PRESENT_MODE_MAILBOX_KHR && HasFpsUnlockChanged(); } } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_swapchain.h b/src/video_core/renderer_vulkan/vk_swapchain.h index cc88b0be2..5821510a1 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.h +++ b/src/video_core/renderer_vulkan/vk_swapchain.h @@ -12,20 +12,29 @@ namespace Vulkan { struct SwapChainImage { vk::Image image; - VKTexture texture; + vk::UniqueImageView image_view; VKFramebuffer framebuffer; }; -class VKSwapchain { +struct SwapChainDetails { + vk::SurfaceFormatKHR format; + vk::PresentModeKHR present_mode; + vk::Extent2D extent; + vk::SurfaceTransformFlagBitsKHR transform; + u32 image_count; +}; + +class VKSwapChain { public: - VKSwapchain(vk::SurfaceKHR surface); - ~VKSwapchain() = default; + VKSwapChain(vk::SurfaceKHR surface); + ~VKSwapChain() = default; /// Creates (or recreates) the swapchain with a given size. - void Create(u32 width, u32 height, bool vsync_enabled); + bool Create(u32 width, u32 height, bool vsync_enabled); - /// Acquires the next image in the swapchain, waits as needed. - void AcquireNextImage(); + /// Acquire the next image in the swapchain. + void AcquireNextImage(vk::Semaphore present_semaphore); + void Present(vk::Semaphore render_semaphore); /// Returns true when the swapchain needs to be recreated. bool NeedsRecreation() const { return IsSubOptimal(); } @@ -35,23 +44,23 @@ public: u32 GetCurrentImageIndex() const { return image_index; } /// Get current swapchain state - vk::Extent2D GetSize() const { return extent; } + vk::Extent2D GetSize() const { return details.extent; } vk::SurfaceKHR GetSurface() const { return surface; } - vk::SurfaceFormatKHR GetSurfaceFormat() const { return surface_format; } - vk::Format GetTextureFormat() const { return texture_format; } + vk::SurfaceFormatKHR GetSurfaceFormat() const { return details.format; } vk::SwapchainKHR GetSwapChain() const { return swapchain.get(); } vk::Image GetCurrentImage() const { return swapchain_images[image_index].image; } /// Retrieve current texture and framebuffer - VKTexture& GetCurrentTexture() { return swapchain_images[image_index].texture; } + vk::Image GetCurrentImage() { return swapchain_images[image_index].image; } VKFramebuffer& GetCurrentFramebuffer() { return swapchain_images[image_index].framebuffer; } private: + void PopulateSwapchainDetails(vk::SurfaceKHR surface, u32 width, u32 height); + void SetupImages(); + +private: + SwapChainDetails details{}; vk::SurfaceKHR surface; - vk::SurfaceFormatKHR surface_format = {}; - vk::PresentModeKHR present_mode = vk::PresentModeKHR::eFifo; - vk::Format texture_format = vk::Format::eUndefined; - vk::Extent2D extent; bool vsync_enabled = false; bool is_outdated = false, is_suboptimal = false; diff --git a/src/video_core/renderer_vulkan/vk_texture.h b/src/video_core/renderer_vulkan/vk_texture.h index 247738d56..7266a80e5 100644 --- a/src/video_core/renderer_vulkan/vk_texture.h +++ b/src/video_core/renderer_vulkan/vk_texture.h @@ -84,8 +84,8 @@ enum Attachments { class VKFramebuffer final : public NonCopyable { public: struct Info { - VKTexture* color; - VKTexture* depth_stencil; + VKTexture* color = nullptr; + VKTexture* depth_stencil = nullptr; }; VKFramebuffer() = default;