video_core: Cleanups and fixes
This commit is contained in:
@@ -95,8 +95,6 @@ add_library(video_core STATIC
|
||||
renderer_opengl/gl_format_reinterpreter.cpp
|
||||
renderer_opengl/gl_format_reinterpreter.h
|
||||
renderer_vulkan/pica_to_vulkan.h
|
||||
renderer_vulkan/renderer_vulkan.cpp
|
||||
renderer_vulkan/renderer_vulkan.h
|
||||
renderer_vulkan/vk_backend.cpp
|
||||
renderer_vulkan/vk_backend.h
|
||||
renderer_vulkan/vk_buffer.cpp
|
||||
@@ -114,17 +112,10 @@ add_library(video_core STATIC
|
||||
renderer_vulkan/vk_pipeline.cpp
|
||||
renderer_vulkan/vk_pipeline.h
|
||||
renderer_vulkan/vk_platform.h
|
||||
renderer_vulkan/vk_rasterizer_cache.cpp
|
||||
renderer_vulkan/vk_rasterizer_cache.h
|
||||
renderer_vulkan/vk_rasterizer.cpp
|
||||
renderer_vulkan/vk_rasterizer.h
|
||||
renderer_vulkan/vk_shader_state.h
|
||||
renderer_vulkan/vk_shader_gen.cpp
|
||||
renderer_vulkan/vk_shader_gen.h
|
||||
renderer_vulkan/vk_shader.cpp
|
||||
renderer_vulkan/vk_shader.h
|
||||
renderer_vulkan/vk_surface_params.cpp
|
||||
renderer_vulkan/vk_surface_params.h
|
||||
renderer_vulkan/vk_swapchain.cpp
|
||||
renderer_vulkan/vk_swapchain.h
|
||||
renderer_vulkan/vk_task_scheduler.cpp
|
||||
|
@@ -16,7 +16,7 @@ namespace VideoCore {
|
||||
|
||||
// A piece of information the video frontend can query the backend about
|
||||
enum class Query {
|
||||
PresentFormat = 0
|
||||
UniformAlignment = 0,
|
||||
};
|
||||
|
||||
// Common interface of a video backend
|
||||
@@ -37,6 +37,9 @@ public:
|
||||
// Asks the driver about a particular piece of information
|
||||
virtual u64 QueryDriver(Query query) = 0;
|
||||
|
||||
// Returns the has of the pipeline info struct accounting for dynamic states
|
||||
virtual u64 PipelineInfoHash(const PipelineInfo& info) = 0;
|
||||
|
||||
// Creates a backend specific texture handle
|
||||
virtual TextureHandle CreateTexture(TextureInfo info) = 0;
|
||||
|
||||
|
@@ -62,19 +62,23 @@ union RasterizationState {
|
||||
BitField<4, 2, Pica::CullMode> cull_mode;
|
||||
};
|
||||
|
||||
union DepthStencilState {
|
||||
u64 value = 0;
|
||||
BitField<0, 1, u64> depth_test_enable;
|
||||
BitField<1, 1, u64> depth_write_enable;
|
||||
BitField<2, 1, u64> stencil_test_enable;
|
||||
BitField<3, 3, Pica::CompareFunc> depth_compare_op;
|
||||
BitField<6, 3, Pica::StencilAction> stencil_fail_op;
|
||||
BitField<9, 3, Pica::StencilAction> stencil_pass_op;
|
||||
BitField<12, 3, Pica::StencilAction> stencil_depth_fail_op;
|
||||
BitField<15, 3, Pica::CompareFunc> stencil_compare_op;
|
||||
BitField<18, 8, u64> stencil_reference;
|
||||
BitField<26, 8, u64> stencil_compare_mask;
|
||||
BitField<34, 8, u64> stencil_write_mask;
|
||||
struct DepthStencilState {
|
||||
union {
|
||||
u32 value = 0;
|
||||
BitField<0, 1, u32> depth_test_enable;
|
||||
BitField<1, 1, u32> depth_write_enable;
|
||||
BitField<2, 1, u32> stencil_test_enable;
|
||||
BitField<3, 3, Pica::CompareFunc> depth_compare_op;
|
||||
BitField<6, 3, Pica::StencilAction> stencil_fail_op;
|
||||
BitField<9, 3, Pica::StencilAction> stencil_pass_op;
|
||||
BitField<12, 3, Pica::StencilAction> stencil_depth_fail_op;
|
||||
BitField<15, 3, Pica::CompareFunc> stencil_compare_op;
|
||||
};
|
||||
|
||||
// These are dynamic on most graphics APIs so keep them separate
|
||||
u8 stencil_reference;
|
||||
u8 stencil_compare_mask;
|
||||
u8 stencil_write_mask;
|
||||
};
|
||||
|
||||
union BlendState {
|
||||
@@ -131,14 +135,10 @@ struct PipelineInfo {
|
||||
VertexLayout vertex_layout{};
|
||||
PipelineLayoutInfo layout{};
|
||||
BlendState blending{};
|
||||
DepthStencilState depth_stencil{};
|
||||
RasterizationState rasterization{};
|
||||
TextureFormat color_attachment = TextureFormat::RGBA8;
|
||||
TextureFormat depth_attachment = TextureFormat::D24S8;
|
||||
|
||||
const u64 Hash() const {
|
||||
return Common::ComputeStructHash64(*this);
|
||||
}
|
||||
RasterizationState rasterization{};
|
||||
DepthStencilState depth_stencil{};
|
||||
};
|
||||
#pragma pack()
|
||||
|
||||
@@ -186,12 +186,3 @@ protected:
|
||||
using PipelineHandle = IntrusivePtr<PipelineBase>;
|
||||
|
||||
} // namespace VideoCore
|
||||
|
||||
namespace std {
|
||||
template <>
|
||||
struct hash<VideoCore::PipelineInfo> {
|
||||
std::size_t operator()(const VideoCore::PipelineInfo& info) const noexcept {
|
||||
return info.Hash();
|
||||
}
|
||||
};
|
||||
} // namespace std
|
||||
|
@@ -1,14 +1,15 @@
|
||||
// Copyright 2018 Citra Emulator Project
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <thread>
|
||||
#include <tuple>
|
||||
#include <mutex>
|
||||
#include "core/frontend/scope_acquire_context.h"
|
||||
#include "video_core/common/pipeline_cache.h"
|
||||
#include "video_core/common/shader.h"
|
||||
#include "video_core/common/shader_gen.h"
|
||||
#include "video_core/renderer_vulkan/vk_shader_gen.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
namespace VideoCore {
|
||||
@@ -41,7 +42,8 @@ PipelineCache::PipelineCache(Frontend::EmuWindow& emu_window, std::unique_ptr<Ba
|
||||
: emu_window(emu_window), backend(backend), pica_vertex_shaders(backend, generator),
|
||||
fixed_geometry_shaders(backend, generator), fragment_shaders(backend, generator),
|
||||
disk_cache(backend) {
|
||||
//generator = std::make_unique<ShaderGenerator
|
||||
// TODO: Don't hardcode this!
|
||||
generator = std::make_unique<Vulkan::ShaderGenerator>();
|
||||
}
|
||||
|
||||
PipelineHandle PipelineCache::GetPipeline(PipelineInfo& info) {
|
||||
@@ -51,7 +53,8 @@ PipelineHandle PipelineCache::GetPipeline(PipelineInfo& info) {
|
||||
info.shaders[static_cast<u32>(ProgramType::FragmentShader)] = current_fragment_shader;
|
||||
|
||||
// Search cache
|
||||
if (auto iter = cached_pipelines.find(info); iter != cached_pipelines.end()) {
|
||||
const u64 pipeline_hash = backend->PipelineInfoHash(info);
|
||||
if (auto iter = cached_pipelines.find(pipeline_hash); iter != cached_pipelines.end()) {
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
@@ -309,7 +312,7 @@ void PipelineCache::LoadDiskCache(const std::atomic_bool& stop_loading, const Di
|
||||
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();
|
||||
//contexts[i] = emu_window.CreateSharedContext();
|
||||
threads[i] = std::thread(LoadRawSepareble, contexts[i].get(), start, end);
|
||||
}
|
||||
|
||||
|
@@ -58,13 +58,13 @@ private:
|
||||
std::unique_ptr<BackendBase>& backend;
|
||||
std::unique_ptr<ShaderGeneratorBase> generator;
|
||||
|
||||
// Keeps all the compiled graphics pipelines
|
||||
std::unordered_map<PipelineInfo, PipelineHandle> cached_pipelines;
|
||||
// Keeps all the compiled graphics pipelines. The hash is decided by the backend
|
||||
std::unordered_map<u64, PipelineHandle, Common::IdentityHash> cached_pipelines;
|
||||
|
||||
// Current shaders
|
||||
ShaderHandle current_vertex_shader;
|
||||
ShaderHandle current_geometry_shader;
|
||||
ShaderHandle current_fragment_shader;
|
||||
ShaderHandle current_vertex_shader{};
|
||||
ShaderHandle current_geometry_shader{};
|
||||
ShaderHandle current_fragment_shader{};
|
||||
|
||||
// Pica runtime shader caches
|
||||
PicaVertexShaders pica_vertex_shaders;
|
||||
|
@@ -184,12 +184,10 @@ Rasterizer::Rasterizer(Frontend::EmuWindow& emu_window, std::unique_ptr<BackendB
|
||||
texel_buffer_lut = backend->CreateBuffer(TEXEL_BUFFER_INFO);
|
||||
texel_buffer_lut_lf = backend->CreateBuffer(TEXEL_BUFFER_LF_INFO);
|
||||
|
||||
/*glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &uniform_buffer_alignment);
|
||||
uniform_size_aligned_vs =
|
||||
Common::AlignUp<std::size_t>(sizeof(VSUniformData), uniform_buffer_alignment);
|
||||
uniform_size_aligned_fs =
|
||||
Common::AlignUp<std::size_t>(sizeof(UniformData), uniform_buffer_alignment);
|
||||
*/
|
||||
// TODO: Have the backend say this
|
||||
uniform_buffer_alignment = 64;
|
||||
uniform_size_aligned_vs = Common::AlignUp<std::size_t>(sizeof(VSUniformData), uniform_buffer_alignment);
|
||||
uniform_size_aligned_fs = Common::AlignUp<std::size_t>(sizeof(UniformData), uniform_buffer_alignment);
|
||||
|
||||
// Create pipeline cache
|
||||
pipeline_cache = std::make_unique<PipelineCache>(emu_window, backend);
|
||||
@@ -587,16 +585,14 @@ bool Rasterizer::Draw(bool accelerate, bool is_indexed) {
|
||||
viewport_rect_unscaled.bottom * res_scale,
|
||||
surfaces_rect.bottom, surfaces_rect.top))}; // Bottom
|
||||
|
||||
// Retrive the framebuffer assigned to the surfaces
|
||||
// Retrieve the framebuffer assigned to the surfaces and update raster_info
|
||||
FramebufferHandle framebuffer = res_cache.GetFramebuffer(color_surface, depth_surface);
|
||||
|
||||
// Sync the viewport
|
||||
const Rect2D viewport = {
|
||||
static_cast<s32>(surfaces_rect.left) + viewport_rect_unscaled.left * res_scale,
|
||||
static_cast<s32>(surfaces_rect.bottom) + viewport_rect_unscaled.bottom * res_scale,
|
||||
static_cast<u32>(viewport_rect_unscaled.GetWidth() * res_scale),
|
||||
static_cast<u32>(viewport_rect_unscaled.GetHeight() * res_scale)
|
||||
};
|
||||
raster_info.color_attachment = framebuffer->GetColorAttachment().IsValid() ?
|
||||
framebuffer->GetColorAttachment()->GetFormat() :
|
||||
TextureFormat::Undefined;
|
||||
raster_info.depth_attachment = framebuffer->GetDepthStencilAttachment().IsValid() ?
|
||||
framebuffer->GetDepthStencilAttachment()->GetFormat() :
|
||||
TextureFormat::Undefined;
|
||||
|
||||
if (uniform_block_data.data.framebuffer_scale != res_scale) {
|
||||
uniform_block_data.data.framebuffer_scale = res_scale;
|
||||
@@ -637,8 +633,12 @@ bool Rasterizer::Draw(bool accelerate, bool is_indexed) {
|
||||
shader_dirty = false;
|
||||
}
|
||||
|
||||
// Sync the viewport
|
||||
PipelineHandle raster_pipeline = pipeline_cache->GetPipeline(raster_info);
|
||||
raster_pipeline->SetViewport(viewport);
|
||||
raster_pipeline->SetViewport(surfaces_rect.left + viewport_rect_unscaled.left * res_scale,
|
||||
surfaces_rect.bottom + viewport_rect_unscaled.bottom * res_scale,
|
||||
viewport_rect_unscaled.GetWidth() * res_scale,
|
||||
viewport_rect_unscaled.GetHeight() * res_scale);
|
||||
|
||||
// Checks if the game is trying to use a surface as a texture and framebuffer at the same time
|
||||
// which causes unpredictable behavior on the host.
|
||||
@@ -803,16 +803,9 @@ bool Rasterizer::Draw(bool accelerate, bool is_indexed) {
|
||||
// Sync the uniform data
|
||||
UploadUniforms(raster_pipeline, accelerate);
|
||||
|
||||
const Common::Rectangle<u32> scissor = {
|
||||
draw_rect.left,
|
||||
draw_rect.bottom,
|
||||
draw_rect.GetWidth(),
|
||||
draw_rect.GetHeight()
|
||||
};
|
||||
|
||||
// Viewport can have negative offsets or larger dimensions than our framebuffer sub-rect.
|
||||
// Enable scissor test to prevent drawing outside of the framebuffer region
|
||||
raster_pipeline->SetScissor(scissor);
|
||||
raster_pipeline->SetScissor(draw_rect.left, draw_rect.bottom, draw_rect.GetWidth(), draw_rect.GetHeight());
|
||||
|
||||
// Draw the vertex batch
|
||||
bool succeeded = true;
|
||||
@@ -1686,15 +1679,15 @@ void Rasterizer::SyncColorWriteMask() {
|
||||
|
||||
void Rasterizer::SyncStencilWriteMask() {
|
||||
const auto& regs = Pica::g_state.regs;
|
||||
raster_info.depth_stencil.stencil_write_mask.Assign(
|
||||
raster_info.depth_stencil.stencil_write_mask =
|
||||
(regs.framebuffer.framebuffer.allow_depth_stencil_write != 0)
|
||||
? static_cast<u32>(regs.framebuffer.output_merger.stencil_test.write_mask)
|
||||
: 0);
|
||||
: 0;
|
||||
}
|
||||
|
||||
void Rasterizer::SyncDepthWriteMask() {
|
||||
const auto& regs = Pica::g_state.regs;
|
||||
raster_info.depth_stencil.stencil_write_mask.Assign(
|
||||
raster_info.depth_stencil.depth_write_enable.Assign(
|
||||
(regs.framebuffer.framebuffer.allow_depth_stencil_write != 0 &&
|
||||
regs.framebuffer.output_merger.depth_write_enable));
|
||||
}
|
||||
@@ -1708,8 +1701,8 @@ void Rasterizer::SyncStencilTest() {
|
||||
raster_info.depth_stencil.stencil_pass_op.Assign(regs.framebuffer.output_merger.stencil_test.action_depth_pass);
|
||||
raster_info.depth_stencil.stencil_depth_fail_op.Assign(regs.framebuffer.output_merger.stencil_test.action_depth_fail);
|
||||
raster_info.depth_stencil.stencil_compare_op.Assign(regs.framebuffer.output_merger.stencil_test.func);
|
||||
raster_info.depth_stencil.stencil_reference.Assign(regs.framebuffer.output_merger.stencil_test.reference_value);
|
||||
raster_info.depth_stencil.stencil_write_mask.Assign(regs.framebuffer.output_merger.stencil_test.input_mask);
|
||||
raster_info.depth_stencil.stencil_reference = regs.framebuffer.output_merger.stencil_test.reference_value;
|
||||
raster_info.depth_stencil.stencil_write_mask = regs.framebuffer.output_merger.stencil_test.input_mask;
|
||||
}
|
||||
|
||||
void Rasterizer::SyncDepthTest() {
|
||||
|
@@ -1456,7 +1456,7 @@ bool RasterizerCache::IntervalHasInvalidPixelFormat(SurfaceParams& params, const
|
||||
|
||||
bool RasterizerCache::ValidateByReinterpretation(const Surface& surface, SurfaceParams& params,
|
||||
const SurfaceInterval& interval) {
|
||||
auto [cvt_begin, cvt_end] =
|
||||
/*auto [cvt_begin, cvt_end] =
|
||||
format_reinterpreter->GetPossibleReinterpretations(surface->pixel_format);
|
||||
for (auto reinterpreter = cvt_begin; reinterpreter != cvt_end; ++reinterpreter) {
|
||||
PixelFormat format = reinterpreter->first.src_format;
|
||||
@@ -1498,7 +1498,7 @@ bool RasterizerCache::ValidateByReinterpretation(const Surface& surface, Surface
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}*/
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@@ -165,11 +165,10 @@ DisplayRenderer::DisplayRenderer(Frontend::EmuWindow& window) : render_window(wi
|
||||
&fragment_shader_anaglyph_source,
|
||||
&fragment_shader_interlaced_source};
|
||||
|
||||
const auto color_format = static_cast<TextureFormat>(backend->QueryDriver(Query::PresentFormat));
|
||||
PipelineInfo present_pipeline_info = {
|
||||
.vertex_layout = ScreenRectVertex::GetVertexLayout(),
|
||||
.layout = RENDERER_PIPELINE_INFO,
|
||||
.color_attachment = color_format,
|
||||
.color_attachment = TextureFormat::PresentColor,
|
||||
.depth_attachment = TextureFormat::Undefined
|
||||
};
|
||||
|
||||
|
@@ -4,9 +4,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <tuple>
|
||||
#include "video_core/common/backend.h"
|
||||
#include "video_core/common/shader_gen.h"
|
||||
|
||||
|
@@ -24,6 +24,7 @@ enum class TextureFormat : u8 {
|
||||
D16 = 5,
|
||||
D24 = 6,
|
||||
D24S8 = 7,
|
||||
PresentColor = 8, // Backend specific swapchain format
|
||||
Undefined = 255
|
||||
};
|
||||
|
||||
|
@@ -385,6 +385,8 @@ FormatReinterpreterOpenGL::FormatReinterpreterOpenGL() {
|
||||
std::make_unique<RGBA4toRGB5A1>());
|
||||
}
|
||||
|
||||
FormatReinterpreterOpenGL::~FormatReinterpreterOpenGL() = default;
|
||||
|
||||
std::pair<FormatReinterpreterOpenGL::ReinterpreterMap::iterator,
|
||||
FormatReinterpreterOpenGL::ReinterpreterMap::iterator>
|
||||
FormatReinterpreterOpenGL::GetPossibleReinterpretations(PixelFormat dst_format) {
|
||||
|
@@ -50,7 +50,7 @@ class FormatReinterpreterOpenGL : NonCopyable {
|
||||
|
||||
public:
|
||||
explicit FormatReinterpreterOpenGL();
|
||||
~FormatReinterpreterOpenGL() = default;
|
||||
~FormatReinterpreterOpenGL();
|
||||
|
||||
std::pair<ReinterpreterMap::iterator, ReinterpreterMap::iterator> GetPossibleReinterpretations(
|
||||
SurfaceParams::PixelFormat dst_format);
|
||||
|
@@ -43,13 +43,10 @@ static bool IsVendorAmd() {
|
||||
const std::string_view gpu_vendor{reinterpret_cast<char const*>(glGetString(GL_VENDOR))};
|
||||
return gpu_vendor == "ATI Technologies Inc." || gpu_vendor == "Advanced Micro Devices, Inc.";
|
||||
}
|
||||
|
||||
#ifdef __APPLE__
|
||||
static bool IsVendorIntel() {
|
||||
std::string gpu_vendor{reinterpret_cast<char const*>(glGetString(GL_VENDOR))};
|
||||
return gpu_vendor == "Intel Inc.";
|
||||
}
|
||||
#endif
|
||||
|
||||
RasterizerOpenGL::RasterizerOpenGL(Frontend::EmuWindow& emu_window)
|
||||
: is_amd(IsVendorAmd()), vertex_buffer(GL_ARRAY_BUFFER, VERTEX_BUFFER_SIZE, is_amd),
|
||||
@@ -414,7 +411,7 @@ bool RasterizerOpenGL::SetupGeometryShader() {
|
||||
MICROPROFILE_SCOPE(OpenGL_GS);
|
||||
const auto& regs = Pica::g_state.regs;
|
||||
|
||||
if (regs.pipeline.use_gs != Pica::UseGS::No) {
|
||||
if (regs.pipeline.use_gs != Pica::PipelineRegs::UseGS::No) {
|
||||
LOG_ERROR(Render_OpenGL, "Accelerate draw doesn't support geometry shader");
|
||||
return false;
|
||||
}
|
||||
@@ -761,9 +758,8 @@ bool RasterizerOpenGL::Draw(bool accelerate, bool is_indexed) {
|
||||
texture_cube_sampler.SyncWithConfig(texture.config);
|
||||
state.texture_units[texture_index].texture_2d = 0;
|
||||
continue; // Texture unit 0 setup finished. Continue to next unit
|
||||
default:
|
||||
state.texture_cube_unit.texture_cube = 0;
|
||||
}
|
||||
state.texture_cube_unit.texture_cube = 0;
|
||||
}
|
||||
|
||||
texture_samplers[texture_index].SyncWithConfig(texture.config);
|
||||
@@ -786,6 +782,12 @@ bool RasterizerOpenGL::Draw(bool accelerate, bool is_indexed) {
|
||||
}
|
||||
}
|
||||
|
||||
if (color_surface->pixel_format == SurfaceParams::PixelFormat::RGB5A1 ||
|
||||
color_surface->pixel_format == SurfaceParams::PixelFormat::RGB565 ||
|
||||
color_surface->pixel_format == SurfaceParams::PixelFormat::RGBA4) {
|
||||
LOG_WARNING(Render_OpenGL, "Render target with unsupported format!\n");
|
||||
}
|
||||
|
||||
OGLTexture temp_tex;
|
||||
if (need_duplicate_texture && (GLAD_GL_ARB_copy_image || GLES)) {
|
||||
// The game is trying to use a surface as a texture and framebuffer at the same time
|
||||
@@ -802,7 +804,7 @@ bool RasterizerOpenGL::Draw(bool accelerate, bool is_indexed) {
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glBindTexture(GL_TEXTURE_2D, state.texture_units[0].texture_2d);
|
||||
|
||||
for (std::size_t level{0}; level <= color_surface->max_level; ++level) {
|
||||
for (u32 level{0}; level <= color_surface->max_level; ++level) {
|
||||
glCopyImageSubData(color_surface->texture.handle, GL_TEXTURE_2D, level, 0, 0, 0,
|
||||
temp_tex.handle, GL_TEXTURE_2D, level, 0, 0, 0,
|
||||
color_surface->GetScaledWidth() >> level,
|
||||
|
@@ -791,6 +791,10 @@ void CachedSurface::UploadGLTexture(Common::Rectangle<u32> rect, GLuint read_fb_
|
||||
const FormatTuple& tuple = GetFormatTuple(pixel_format);
|
||||
GLuint target_tex = texture.handle;
|
||||
|
||||
if (addr == 0x1829bfc0) {
|
||||
LOG_ERROR(Render_OpenGL, "Framebuffer upload!");
|
||||
}
|
||||
|
||||
// If not 1x scale, create 1x texture that we will blit from to replace texture subrect in
|
||||
// surface
|
||||
OGLTexture unscaled_tex;
|
||||
|
@@ -9,18 +9,19 @@
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <fmt/format.h>
|
||||
#include <nihstro/shader_bytecode.h>
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/shader_compiler/frontend/opcode.h"
|
||||
#include "video_core/shader_compiler/frontned/instruction.h"
|
||||
#include "video_core/shader_compiler/frontend/register.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_decompiler.h"
|
||||
|
||||
using Pica::Shader::OpCode;
|
||||
using Pica::Shader::DestRegister;
|
||||
|
||||
namespace OpenGL::ShaderDecompiler {
|
||||
|
||||
using nihstro::Instruction;
|
||||
using nihstro::OpCode;
|
||||
using nihstro::RegisterType;
|
||||
using nihstro::SourceRegister;
|
||||
using nihstro::SwizzlePattern;
|
||||
|
||||
constexpr u32 PROGRAM_END = Pica::Shader::MAX_PROGRAM_CODE_LENGTH;
|
||||
|
||||
class DecompileFail : public std::runtime_error {
|
||||
|
@@ -9,7 +9,7 @@
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/shader_compiler/shader.h"
|
||||
#include "video_core/shader/shader.h"
|
||||
|
||||
namespace OpenGL::ShaderDecompiler {
|
||||
|
||||
|
@@ -66,7 +66,6 @@ bool ShaderDiskCacheRaw::Load(FileUtil::IOFile& file) {
|
||||
if (file.ReadBytes(&code_len, sizeof(u64)) != sizeof(u64)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
program_code.resize(code_len);
|
||||
if (file.ReadArray(program_code.data(), code_len) != code_len) {
|
||||
return false;
|
||||
|
@@ -41,17 +41,16 @@ using ProgramCode = std::vector<u32>;
|
||||
using ShaderDecompiledMap = std::unordered_map<u64, ShaderDiskCacheDecompiled>;
|
||||
using ShaderDumpsMap = std::unordered_map<u64, ShaderDiskCacheDump>;
|
||||
|
||||
// Describes a shader how it's used by the guest GPU
|
||||
/// Describes a shader how it's used by the guest GPU
|
||||
class ShaderDiskCacheRaw {
|
||||
public:
|
||||
explicit ShaderDiskCacheRaw(u64 unique_identifier, ProgramType program_type,
|
||||
RawShaderConfig config, ProgramCode program_code);
|
||||
ShaderDiskCacheRaw() = default;
|
||||
ShaderDiskCacheRaw(u64 unique_identifier, ProgramType program_type,
|
||||
Pica::Regs config, std::vector<u32> program_code) :
|
||||
unique_identifier(unique_identifier), program_type(program_type), config(config),
|
||||
program_code(program_code) {}
|
||||
~ShaderDiskCacheRaw() = default;
|
||||
|
||||
bool Load(FileUtil::IOFile& file);
|
||||
|
||||
bool Save(FileUtil::IOFile& file) const;
|
||||
|
||||
u64 GetUniqueIdentifier() const {
|
||||
@@ -62,19 +61,19 @@ public:
|
||||
return program_type;
|
||||
}
|
||||
|
||||
const std::vector<u32>& GetProgramCode() const {
|
||||
const ProgramCode& GetProgramCode() const {
|
||||
return program_code;
|
||||
}
|
||||
|
||||
const Pica::Regs& GetRawShaderConfig() const {
|
||||
const RawShaderConfig& GetRawShaderConfig() const {
|
||||
return config;
|
||||
}
|
||||
|
||||
private:
|
||||
u64 unique_identifier = 0;
|
||||
u64 unique_identifier{};
|
||||
ProgramType program_type{};
|
||||
Pica::Regs config{};
|
||||
std::vector<u32> program_code{};
|
||||
RawShaderConfig config{};
|
||||
ProgramCode program_code{};
|
||||
};
|
||||
|
||||
/// Contains decompiled data from a shader
|
||||
|
@@ -175,11 +175,11 @@ public:
|
||||
|
||||
void Create(const char* source, GLenum type) {
|
||||
if (shader_or_program.which() == 0) {
|
||||
std::get<OGLShader>(shader_or_program).Create(source, type);
|
||||
boost::get<OGLShader>(shader_or_program).Create(source, type);
|
||||
} else {
|
||||
OGLShader shader;
|
||||
shader.Create(source, type);
|
||||
OGLProgram& program = std::get<OGLProgram>(shader_or_program);
|
||||
OGLProgram& program = boost::get<OGLProgram>(shader_or_program);
|
||||
program.Create(true, {shader.handle});
|
||||
SetShaderUniformBlockBindings(program.handle);
|
||||
|
||||
@@ -191,9 +191,9 @@ public:
|
||||
|
||||
GLuint GetHandle() const {
|
||||
if (shader_or_program.which() == 0) {
|
||||
return std::get<OGLShader>(shader_or_program).handle;
|
||||
return boost::get<OGLShader>(shader_or_program).handle;
|
||||
} else {
|
||||
return std::get<OGLProgram>(shader_or_program).handle;
|
||||
return boost::get<OGLProgram>(shader_or_program).handle;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,7 +204,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
std::variant<OGLShader, OGLProgram> shader_or_program;
|
||||
boost::variant<OGLShader, OGLProgram> shader_or_program;
|
||||
};
|
||||
|
||||
class TrivialVertexShader {
|
||||
@@ -393,7 +393,7 @@ bool ShaderProgramManager::UseProgrammableVertexShader(const Pica::Regs& regs,
|
||||
// Save VS to the disk cache if its a new shader
|
||||
if (result) {
|
||||
auto& disk_cache = impl->disk_cache;
|
||||
std::vector<u32> program_code{setup.program_code.begin(), setup.program_code.end()};
|
||||
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);
|
||||
@@ -715,7 +715,6 @@ void ShaderProgramManager::LoadDiskCache(const std::atomic_bool& stop_loading,
|
||||
contexts[i] = emu_window.CreateSharedContext();
|
||||
threads[i] = std::thread(LoadRawSepareble, contexts[i].get(), start, end);
|
||||
}
|
||||
|
||||
for (auto& thread : threads) {
|
||||
thread.join();
|
||||
}
|
||||
|
@@ -5,7 +5,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <array>
|
||||
#include <glad/glad.h>
|
||||
#include "video_core/rasterizer_interface.h"
|
||||
#include "video_core/regs_lighting.h"
|
||||
@@ -23,39 +22,39 @@ namespace OpenGL {
|
||||
enum class UniformBindings : GLuint { Common, VS, GS };
|
||||
|
||||
struct LightSrc {
|
||||
alignas(16) Common::Vec3f specular_0;
|
||||
alignas(16) Common::Vec3f specular_1;
|
||||
alignas(16) Common::Vec3f diffuse;
|
||||
alignas(16) Common::Vec3f ambient;
|
||||
alignas(16) Common::Vec3f position;
|
||||
alignas(16) Common::Vec3f spot_direction; // negated
|
||||
float dist_atten_bias;
|
||||
float dist_atten_scale;
|
||||
alignas(16) GLvec3 specular_0;
|
||||
alignas(16) GLvec3 specular_1;
|
||||
alignas(16) GLvec3 diffuse;
|
||||
alignas(16) GLvec3 ambient;
|
||||
alignas(16) GLvec3 position;
|
||||
alignas(16) GLvec3 spot_direction; // negated
|
||||
GLfloat dist_atten_bias;
|
||||
GLfloat dist_atten_scale;
|
||||
};
|
||||
|
||||
// Uniform structure for the Uniform Buffer Object, all vectors must be 16-byte aligned
|
||||
/// 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;
|
||||
GLint framebuffer_scale;
|
||||
GLint alphatest_ref;
|
||||
GLfloat depth_scale;
|
||||
GLfloat depth_offset;
|
||||
GLfloat shadow_bias_constant;
|
||||
GLfloat shadow_bias_linear;
|
||||
GLint scissor_x1;
|
||||
GLint scissor_y1;
|
||||
GLint scissor_x2;
|
||||
GLint scissor_y2;
|
||||
GLint fog_lut_offset;
|
||||
GLint proctex_noise_lut_offset;
|
||||
GLint proctex_color_map_offset;
|
||||
GLint proctex_alpha_map_offset;
|
||||
GLint proctex_lut_offset;
|
||||
GLint proctex_diff_lut_offset;
|
||||
GLfloat proctex_bias;
|
||||
GLint shadow_texture_bias;
|
||||
alignas(16) GLivec4 lighting_lut_offset[Pica::LightingRegs::NumLightingSampler / 4];
|
||||
alignas(16) GLvec3 fog_color;
|
||||
alignas(8) GLvec2 proctex_noise_f;
|
||||
@@ -63,29 +62,29 @@ struct UniformData {
|
||||
alignas(8) GLvec2 proctex_noise_p;
|
||||
alignas(16) GLvec3 lighting_global_ambient;
|
||||
LightSrc light_src[8];
|
||||
alignas(16) Common::Vec4f const_color[6]; // A vec4 color for each of the six tev stages
|
||||
alignas(16) Common::Vec4f tev_combiner_buffer_color;
|
||||
alignas(16) Common::Vec4f clip_coef;
|
||||
alignas(16) GLvec4 const_color[6]; // A vec4 color for each of the six tev stages
|
||||
alignas(16) GLvec4 tev_combiner_buffer_color;
|
||||
alignas(16) GLvec4 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) == 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.
|
||||
/// 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;
|
||||
alignas(16) GLint b;
|
||||
};
|
||||
GLvec4
|
||||
|
||||
std::array<BoolAligned, 16> bools;
|
||||
alignas(16) std::array<Common::Vec4u, 4> i;
|
||||
alignas(16) std::array<Common::Vec4f, 96> f;
|
||||
alignas(16) std::array<GLuvec4, 4> i;
|
||||
alignas(16) std::array<GLvec4, 96> f;
|
||||
};
|
||||
|
||||
struct VSUniformData {
|
||||
|
@@ -312,11 +312,10 @@ uniform int reverse_interlaced;
|
||||
|
||||
void main() {
|
||||
float screen_row = o_resolution.x * frag_tex_coord.x;
|
||||
if (int(screen_row) % 2 == reverse_interlaced) {
|
||||
if (int(screen_row) % 2 == reverse_interlaced)
|
||||
color = texture(color_texture, frag_tex_coord);
|
||||
} else {
|
||||
else
|
||||
color = texture(color_texture_r, frag_tex_coord);
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
|
@@ -57,7 +57,7 @@ struct PresentationTexture {
|
||||
OGLTexture texture;
|
||||
};
|
||||
|
||||
class RendererOpenGL : public VideoCore::RendererBase {
|
||||
class RendererOpenGL : public RendererBase {
|
||||
public:
|
||||
explicit RendererOpenGL(Frontend::EmuWindow& window);
|
||||
~RendererOpenGL() override;
|
||||
|
@@ -50,16 +50,18 @@ void TextureDownloaderES::Test() {
|
||||
state.Apply();
|
||||
|
||||
original_data.resize(tex_size * tex_size);
|
||||
for (std::size_t idx = 0; idx < original_data.size(); ++idx)
|
||||
for (std::size_t idx = 0; idx < original_data.size(); ++idx) {
|
||||
original_data[idx] = data_generator(idx);
|
||||
glTexStorage2D(GL_TEXTURE_2D, 1, tuple.internal_format, tex_size, tex_size);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, tex_size, tex_size, tuple.format, tuple.type,
|
||||
}
|
||||
GLsizei tex_sizei = static_cast<GLsizei>(tex_size);
|
||||
glTexStorage2D(GL_TEXTURE_2D, 1, tuple.internal_format, tex_sizei, tex_sizei);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, tex_sizei, tex_sizei, tuple.format, tuple.type,
|
||||
original_data.data());
|
||||
|
||||
decltype(original_data) new_data(original_data.size());
|
||||
glFinish();
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
GetTexImage(GL_TEXTURE_2D, 0, tuple.format, tuple.type, tex_size, tex_size,
|
||||
GetTexImage(GL_TEXTURE_2D, 0, tuple.format, tuple.type, tex_sizei, tex_sizei,
|
||||
new_data.data());
|
||||
glFinish();
|
||||
auto time = std::chrono::high_resolution_clock::now() - start;
|
||||
|
@@ -106,9 +106,22 @@ Backend::~Backend() {
|
||||
}
|
||||
}
|
||||
|
||||
u64 Backend::PipelineInfoHash(const PipelineInfo& info) {
|
||||
const bool hash_all = !instance.IsExtendedDynamicStateSupported();
|
||||
if (hash_all) {
|
||||
// Don't hash the last three members of DepthStencilState, these are
|
||||
// dynamic in every Vulkan implementation
|
||||
return Common::ComputeHash64(&info, offsetof(PipelineInfo, depth_stencil) +
|
||||
offsetof(DepthStencilState, stencil_reference));
|
||||
} else {
|
||||
// Hash everything except depth_stencil and rasterization
|
||||
return Common::ComputeHash64(&info, offsetof(PipelineInfo, rasterization));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* To avoid many small heap allocations during handle creation, each resource has a dedicated pool
|
||||
* associated with it that batch allocates memory.
|
||||
* associated with it that batch-allocates memory.
|
||||
*/
|
||||
BufferHandle Backend::CreateBuffer(BufferInfo info) {
|
||||
static ObjectPool<Buffer> buffer_pool;
|
||||
@@ -137,15 +150,13 @@ PipelineHandle Backend::CreatePipeline(PipelineType type, PipelineInfo info) {
|
||||
// Get renderpass
|
||||
vk::RenderPass renderpass = GetRenderPass(info.color_attachment, info.depth_attachment);
|
||||
|
||||
// Find a pipeline layout first
|
||||
if (auto iter = pipeline_layouts.find(info.layout); iter != pipeline_layouts.end()) {
|
||||
PipelineLayout& layout = iter->second;
|
||||
|
||||
return PipelineHandle{pipeline_pool.Allocate(instance, layout, type, info, renderpass, cache)};
|
||||
// Find an owner first
|
||||
if (auto iter = pipeline_owners.find(info.layout); iter != pipeline_owners.end()) {
|
||||
return PipelineHandle{pipeline_pool.Allocate(instance, iter->second, type, info, renderpass, cache)};
|
||||
}
|
||||
|
||||
// Create the layout
|
||||
auto result = pipeline_layouts.emplace(info.layout, PipelineLayout{instance, info.layout});
|
||||
auto result = pipeline_owners.emplace(info.layout, PipelineOwner{instance, info.layout});
|
||||
return PipelineHandle{pipeline_pool.Allocate(instance, result.first->second, type, info, renderpass, cache)};
|
||||
}
|
||||
|
||||
@@ -155,7 +166,6 @@ SamplerHandle Backend::CreateSampler(SamplerInfo info) {
|
||||
}
|
||||
|
||||
void Backend::Draw(PipelineHandle pipeline_handle, FramebufferHandle draw_framebuffer,
|
||||
BufferHandle vertex_buffer,
|
||||
u32 base_vertex, u32 num_vertices) {
|
||||
// Bind descriptor sets
|
||||
vk::CommandBuffer command_buffer = scheduler.GetRenderCommandBuffer();
|
||||
|
@@ -16,31 +16,35 @@ namespace VideoCore::Vulkan {
|
||||
class Texture;
|
||||
|
||||
constexpr u32 RENDERPASS_COUNT = (MAX_COLOR_FORMATS + 1) * (MAX_DEPTH_FORMATS + 1);
|
||||
constexpr u32 DESCRIPTOR_BANK_SIZE = 64;
|
||||
|
||||
class Backend final : public VideoCore::BackendBase {
|
||||
public:
|
||||
Backend(Frontend::EmuWindow& window);
|
||||
~Backend();
|
||||
|
||||
void SwapBuffers() override;
|
||||
bool BeginPresent() override;
|
||||
void EndPresent() override;
|
||||
|
||||
FramebufferHandle GetWindowFramebuffer() override;
|
||||
|
||||
u64 QueryDriver(Query query) override;
|
||||
|
||||
u64 PipelineInfoHash(const PipelineInfo& info) override;
|
||||
|
||||
BufferHandle CreateBuffer(BufferInfo info) override;
|
||||
|
||||
FramebufferHandle CreateFramebuffer(FramebufferInfo info) override;
|
||||
|
||||
TextureHandle CreateTexture(TextureInfo info) override;
|
||||
|
||||
PipelineHandle CreatePipeline(PipelineType type, PipelineInfo info) override;
|
||||
|
||||
SamplerHandle CreateSampler(SamplerInfo info) override;
|
||||
ShaderHandle CreateShader(ShaderStage stage, std::string_view name, std::string source) override;
|
||||
|
||||
void BindVertexBuffer(BufferHandle buffer, std::span<const u32> offsets) override;
|
||||
void BindIndexBuffer(BufferHandle buffer, AttribType index_type, u32 offset) override;
|
||||
|
||||
void Draw(PipelineHandle pipeline, FramebufferHandle draw_framebuffer,
|
||||
BufferHandle vertex_buffer,
|
||||
u32 base_vertex, u32 num_vertices) override;
|
||||
|
||||
void DrawIndexed(PipelineHandle pipeline, FramebufferHandle draw_framebuffer,
|
||||
BufferHandle vertex_buffer, BufferHandle index_buffer, AttribType index_type,
|
||||
u32 base_index, u32 num_indices, u32 base_vertex) override;
|
||||
|
||||
void DispatchCompute(PipelineHandle pipeline, Common::Vec3<u32> groupsize,
|
||||
@@ -73,8 +77,8 @@ private:
|
||||
std::array<vk::RenderPass, RENDERPASS_COUNT> renderpass_cache;
|
||||
vk::PipelineCache cache;
|
||||
|
||||
// Pipeline layout cache
|
||||
std::unordered_map<PipelineLayoutInfo, PipelineLayout> pipeline_layouts;
|
||||
// A cache of pipeline owners
|
||||
std::unordered_map<PipelineLayoutInfo, PipelineOwner> pipeline_owners;
|
||||
|
||||
// Descriptor pools
|
||||
std::array<vk::DescriptorPool, SCHEDULER_COMMAND_COUNT> descriptor_pools;
|
||||
|
@@ -10,12 +10,10 @@
|
||||
#include "video_core/renderer_vulkan/vk_texture.h"
|
||||
#include "video_core/renderer_vulkan/vk_buffer.h"
|
||||
#include "video_core/renderer_vulkan/vk_instance.h"
|
||||
#include "video_core/renderer_vulkan/vk_task_scheduler.h"
|
||||
|
||||
namespace VideoCore::Vulkan {
|
||||
|
||||
// Maximum binding per descriptor set
|
||||
constexpr u32 MAX_BINDING_SLOTS = 7;
|
||||
|
||||
vk::ShaderStageFlags ToVkStageFlags(BindingType type) {
|
||||
vk::ShaderStageFlags flags;
|
||||
switch (type) {
|
||||
@@ -62,21 +60,21 @@ vk::DescriptorType ToVkDescriptorType(BindingType type) {
|
||||
u32 AttribBytes(VertexAttribute attrib) {
|
||||
switch (attrib.type) {
|
||||
case AttribType::Float:
|
||||
return sizeof(float) * attrib.components;
|
||||
return sizeof(float) * attrib.size;
|
||||
case AttribType::Int:
|
||||
return sizeof(u32) * attrib.components;
|
||||
return sizeof(u32) * attrib.size;
|
||||
case AttribType::Short:
|
||||
return sizeof(u16) * attrib.components;
|
||||
return sizeof(u16) * attrib.size;
|
||||
case AttribType::Byte:
|
||||
case AttribType::Ubyte:
|
||||
return sizeof(u8) * attrib.components;
|
||||
return sizeof(u8) * attrib.size;
|
||||
}
|
||||
}
|
||||
|
||||
vk::Format ToVkAttributeFormat(VertexAttribute attrib) {
|
||||
switch (attrib.type) {
|
||||
case AttribType::Float:
|
||||
switch (attrib.components) {
|
||||
switch (attrib.size) {
|
||||
case 1: return vk::Format::eR32Sfloat;
|
||||
case 2: return vk::Format::eR32G32Sfloat;
|
||||
case 3: return vk::Format::eR32G32B32Sfloat;
|
||||
@@ -104,20 +102,20 @@ vk::ShaderStageFlagBits ToVkShaderStage(ShaderStage stage) {
|
||||
}
|
||||
}
|
||||
|
||||
PipelineLayout::PipelineLayout(Instance& instance, PipelineLayoutInfo info) :
|
||||
PipelineOwner::PipelineOwner(Instance& instance, PipelineLayoutInfo info) :
|
||||
instance(instance), set_layout_count(info.group_count) {
|
||||
|
||||
// Used as temp storage for CreateDescriptorSet
|
||||
std::array<vk::DescriptorSetLayoutBinding, MAX_BINDING_SLOTS> set_bindings;
|
||||
std::array<vk::DescriptorUpdateTemplateEntry, MAX_BINDING_SLOTS> update_entries;
|
||||
std::array<vk::DescriptorSetLayoutBinding, MAX_BINDINGS_IN_GROUP> set_bindings;
|
||||
std::array<vk::DescriptorUpdateTemplateEntry, MAX_BINDINGS_IN_GROUP> update_entries;
|
||||
|
||||
vk::Device device = instance.GetDevice();
|
||||
for (u32 set = 0; set < set_layout_count; set++) {
|
||||
auto& group = info.binding_groups[set];
|
||||
|
||||
u32 binding = 0;
|
||||
while (group[binding] != BindingType::None) {
|
||||
const BindingType type = group[binding];
|
||||
while (group.Value(binding) != BindingType::None) {
|
||||
const BindingType type = group.Value(binding);
|
||||
set_bindings[binding] = vk::DescriptorSetLayoutBinding{
|
||||
.binding = binding,
|
||||
.descriptorType = ToVkDescriptorType(type),
|
||||
@@ -175,7 +173,7 @@ PipelineLayout::PipelineLayout(Instance& instance, PipelineLayoutInfo info) :
|
||||
pipeline_layout = device.createPipelineLayout(layout_info);
|
||||
}
|
||||
|
||||
PipelineLayout::~PipelineLayout() {
|
||||
PipelineOwner::~PipelineOwner() {
|
||||
vk::Device device = instance.GetDevice();
|
||||
device.destroyPipelineLayout(pipeline_layout);
|
||||
|
||||
@@ -186,9 +184,10 @@ PipelineLayout::~PipelineLayout() {
|
||||
}
|
||||
}
|
||||
|
||||
Pipeline::Pipeline(Instance& instance, PipelineLayout& owner, PipelineType type, PipelineInfo info,
|
||||
Pipeline::Pipeline(Instance& instance, CommandScheduler& scheduler, PipelineOwner& owner,
|
||||
PipelineType type, PipelineInfo info,
|
||||
vk::RenderPass renderpass, vk::PipelineCache cache) : PipelineBase(type, info),
|
||||
instance(instance), owner(owner) {
|
||||
instance(instance), scheduler(scheduler), owner(owner) {
|
||||
|
||||
vk::Device device = instance.GetDevice();
|
||||
|
||||
@@ -209,43 +208,41 @@ Pipeline::Pipeline(Instance& instance, PipelineLayout& owner, PipelineType type,
|
||||
};
|
||||
}
|
||||
|
||||
// Create a graphics pipeline
|
||||
if (type == PipelineType::Graphics) {
|
||||
|
||||
/**
|
||||
* Most modern graphics APIs don't natively support constant attributes. To avoid duplicating
|
||||
* the data and increasing data bandwith, we reserve the last binding for fixed attributes,
|
||||
* which are always interleaved and specify VK_VERTEX_INPUT_RATE_INSTANCE as the input rate.
|
||||
* Since we are always rendering 1 instance, the shader will always read the single attribute
|
||||
* Vulkan doesn't intuitively support fixed attributes. To avoid duplicating the data and increasing
|
||||
* data upload, when the fixed flag is true, we specify VK_VERTEX_INPUT_RATE_INSTANCE as the input rate.
|
||||
* Since 1 instance is all we render, the shader will always read the single attribute.
|
||||
*/
|
||||
const vk::VertexInputBindingDescription binding_desc = {
|
||||
.binding = 0,
|
||||
.stride = info.vertex_layout.stride
|
||||
};
|
||||
std::array<vk::VertexInputBindingDescription, MAX_VERTEX_BINDINGS> bindings;
|
||||
for (u32 i = 0; i < info.vertex_layout.binding_count; i++) {
|
||||
const auto& binding = info.vertex_layout.bindings[i];
|
||||
bindings[i] = vk::VertexInputBindingDescription{
|
||||
.binding = binding.binding,
|
||||
.stride = binding.stride,
|
||||
.inputRate = binding.fixed.Value() ? vk::VertexInputRate::eInstance
|
||||
: vk::VertexInputRate::eVertex
|
||||
};
|
||||
}
|
||||
|
||||
// Populate vertex attribute structures
|
||||
u32 attribute_count = 0;
|
||||
std::array<vk::VertexInputAttributeDescription, MAX_VERTEX_ATTRIBUTES> attribute_desc;
|
||||
for (u32 i = 0; i < MAX_VERTEX_ATTRIBUTES; i++) {
|
||||
auto& attr = info.vertex_layout.attributes[i];
|
||||
if (attr.components == 0) {
|
||||
attribute_count = i;
|
||||
break;
|
||||
}
|
||||
|
||||
attribute_desc[i] = vk::VertexInputAttributeDescription{
|
||||
.location = i,
|
||||
.binding = 0,
|
||||
std::array<vk::VertexInputAttributeDescription, MAX_VERTEX_ATTRIBUTES> attributes;
|
||||
for (u32 i = 0; i < info.vertex_layout.attribute_count; i++) {
|
||||
const auto& attr = info.vertex_layout.attributes[i];
|
||||
attributes[i] = vk::VertexInputAttributeDescription{
|
||||
.location = attr.location,
|
||||
.binding = attr.binding,
|
||||
.format = ToVkAttributeFormat(attr),
|
||||
.offset = (i > 0 ? attribute_desc[i - 1].offset +
|
||||
AttribBytes(info.vertex_layout.attributes[i - 1]) : 0)
|
||||
.offset = attr.offset
|
||||
};
|
||||
}
|
||||
|
||||
const vk::PipelineVertexInputStateCreateInfo vertex_input_info = {
|
||||
.vertexBindingDescriptionCount = 1,
|
||||
.pVertexBindingDescriptions = &binding_desc,
|
||||
.vertexAttributeDescriptionCount = attribute_count,
|
||||
.pVertexAttributeDescriptions = attribute_desc.data()
|
||||
.vertexBindingDescriptionCount = info.vertex_layout.binding_count,
|
||||
.pVertexBindingDescriptions = bindings.data(),
|
||||
.vertexAttributeDescriptionCount = info.vertex_layout.attribute_count,
|
||||
.pVertexAttributeDescriptions = attributes.data()
|
||||
};
|
||||
|
||||
const vk::PipelineInputAssemblyStateCreateInfo input_assembly = {
|
||||
@@ -279,8 +276,8 @@ Pipeline::Pipeline(Instance& instance, PipelineLayout& owner, PipelineType type,
|
||||
};
|
||||
|
||||
const vk::PipelineColorBlendStateCreateInfo color_blending = {
|
||||
.logicOpEnable = true,
|
||||
.logicOp = vk::LogicOp::eCopy, // TODO
|
||||
.logicOpEnable = info.blending.logic_op_enable.Value(),
|
||||
.logicOp = PicaToVK::LogicOp(info.blending.logic_op), // TODO
|
||||
.attachmentCount = 1,
|
||||
.pAttachments = &colorblend_attachment,
|
||||
};
|
||||
@@ -385,10 +382,11 @@ void Pipeline::BindTexture(u32 group, u32 slot, TextureHandle handle) {
|
||||
owner.SetBinding(group, slot, data);
|
||||
}
|
||||
|
||||
void Pipeline::BindBuffer(u32 group, u32 slot, BufferHandle handle, u32 view) {
|
||||
void Pipeline::BindBuffer(u32 group, u32 slot, BufferHandle handle, u32 offset, u32 range, u32 view) {
|
||||
Buffer* buffer = static_cast<Buffer*>(handle.Get());
|
||||
|
||||
// Texel buffers are bound with their views
|
||||
// TODO: Support variable binding range?
|
||||
if (buffer->GetUsage() == BufferUsage::Texel) {
|
||||
const DescriptorData data = {
|
||||
.buffer_view = buffer->GetView(view)
|
||||
@@ -399,8 +397,8 @@ void Pipeline::BindBuffer(u32 group, u32 slot, BufferHandle handle, u32 view) {
|
||||
const DescriptorData data = {
|
||||
.buffer_info = vk::DescriptorBufferInfo{
|
||||
.buffer = buffer->GetHandle(),
|
||||
.offset = 0,
|
||||
.range = buffer->GetCapacity()
|
||||
.offset = offset,
|
||||
.range = (range == WHOLE_SIZE ? buffer->GetCapacity() : range)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -420,4 +418,22 @@ void Pipeline::BindSampler(u32 group, u32 slot, SamplerHandle handle) {
|
||||
owner.SetBinding(group, slot, data);
|
||||
}
|
||||
|
||||
void Pipeline::BindPushConstant(std::span<const std::byte> data) {
|
||||
vk::CommandBuffer command_buffer = scheduler.GetRenderCommandBuffer();
|
||||
command_buffer.pushConstants(owner.GetLayout(),
|
||||
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment,
|
||||
0, data.size(), data.data());
|
||||
}
|
||||
|
||||
// Viewport and scissor are always dynamic
|
||||
void Pipeline::SetViewport(float x, float y, float width, float height) {
|
||||
vk::CommandBuffer command_buffer = scheduler.GetRenderCommandBuffer();
|
||||
command_buffer.setViewport(0, vk::Viewport{x, y, width, height, 0.f, 1.f});
|
||||
}
|
||||
|
||||
void Pipeline::SetScissor(s32 x, s32 y, u32 width, u32 height) {
|
||||
vk::CommandBuffer command_buffer = scheduler.GetRenderCommandBuffer();
|
||||
command_buffer.setScissor(0, vk::Rect2D{{x, y}, {width, height}});
|
||||
}
|
||||
|
||||
} // namespace VideoCore::Vulkan
|
||||
|
@@ -14,7 +14,7 @@ class Instance;
|
||||
class CommandScheduler;
|
||||
|
||||
union DescriptorData {
|
||||
vk::DescriptorImageInfo image_info{};
|
||||
vk::DescriptorImageInfo image_info;
|
||||
vk::DescriptorBufferInfo buffer_info;
|
||||
vk::BufferView buffer_view;
|
||||
};
|
||||
@@ -24,18 +24,19 @@ union DescriptorData {
|
||||
* and update templates associated with those layouts.
|
||||
* Functions as the "parent" to a group of pipelines that share the same layout
|
||||
*/
|
||||
class PipelineLayout {
|
||||
class PipelineOwner {
|
||||
public:
|
||||
PipelineLayout(Instance& instance, PipelineLayoutInfo info);
|
||||
~PipelineLayout();
|
||||
PipelineOwner(Instance& instance, PipelineLayoutInfo info);
|
||||
~PipelineOwner();
|
||||
|
||||
// Disable copy constructor
|
||||
PipelineLayout(const PipelineLayout&) = delete;
|
||||
PipelineLayout& operator=(const PipelineLayout&) = delete;
|
||||
PipelineOwner(const PipelineOwner&) = delete;
|
||||
PipelineOwner& operator=(const PipelineOwner&) = delete;
|
||||
|
||||
// Assigns data to a particular binding
|
||||
void SetBinding(u32 set, u32 binding, DescriptorData data) {
|
||||
update_data[set][binding] = data;
|
||||
descriptor_dirty[set] = true;
|
||||
}
|
||||
|
||||
// Returns the number of descriptor set layouts
|
||||
@@ -68,26 +69,32 @@ private:
|
||||
u32 set_layout_count = 0;
|
||||
std::array<vk::DescriptorSetLayout, MAX_BINDING_GROUPS> set_layouts;
|
||||
std::array<vk::DescriptorUpdateTemplate, MAX_BINDING_GROUPS> update_templates;
|
||||
std::array<vk::DescriptorSet, MAX_BINDING_GROUPS> descriptor_bank;
|
||||
|
||||
// Update data for the descriptor sets
|
||||
using SetData = std::array<DescriptorData, MAX_BINDINGS_IN_GROUP>;
|
||||
std::array<SetData, MAX_BINDING_GROUPS> update_data;
|
||||
std::array<SetData, MAX_BINDING_GROUPS> update_data{};
|
||||
std::array<bool, MAX_BINDING_GROUPS> descriptor_dirty{true};
|
||||
};
|
||||
|
||||
class Pipeline : public VideoCore::PipelineBase {
|
||||
public:
|
||||
Pipeline(Instance& instance, PipelineLayout& owner, PipelineType type, PipelineInfo info,
|
||||
Pipeline(Instance& instance, CommandScheduler& scheduler, PipelineOwner& owner,
|
||||
PipelineType type, PipelineInfo info,
|
||||
vk::RenderPass renderpass, vk::PipelineCache cache);
|
||||
~Pipeline() override;
|
||||
|
||||
void BindTexture(u32 group, u32 slot, TextureHandle handle) override;
|
||||
virtual void BindTexture(u32 group, u32 slot, TextureHandle handle) override;
|
||||
virtual void BindBuffer(u32 group, u32 slot, BufferHandle handle,
|
||||
u32 offset = 0, u32 range = WHOLE_SIZE, u32 view = 0) override;
|
||||
virtual void BindSampler(u32 group, u32 slot, SamplerHandle handle) override;
|
||||
virtual void BindPushConstant(std::span<const std::byte> data) override;
|
||||
|
||||
void BindBuffer(u32 group, u32 slot, BufferHandle handle, u32 view = 0) override;
|
||||
|
||||
void BindSampler(u32 group, u32 slot, SamplerHandle handle) override;
|
||||
virtual void SetViewport(float x, float y, float width, float height) override;
|
||||
virtual void SetScissor(s32 x, s32 y, u32 width, u32 height) override;
|
||||
|
||||
/// Returns the layout tracker that owns this pipeline
|
||||
PipelineLayout& GetOwner() const {
|
||||
PipelineOwner& GetOwner() const {
|
||||
return owner;
|
||||
}
|
||||
|
||||
@@ -98,7 +105,8 @@ public:
|
||||
|
||||
private:
|
||||
Instance& instance;
|
||||
PipelineLayout& owner;
|
||||
CommandScheduler& scheduler;
|
||||
PipelineOwner& owner;
|
||||
vk::Pipeline pipeline;
|
||||
};
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -1,285 +0,0 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include <glm/glm.hpp>
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/rasterizer_interface.h"
|
||||
#include "video_core/regs_lighting.h"
|
||||
#include "video_core/regs_texturing.h"
|
||||
#include "video_core/shader/shader.h"
|
||||
#include "video_core/renderer_vulkan/vk_state.h"
|
||||
#include "video_core/renderer_vulkan/vk_rasterizer_cache.h"
|
||||
|
||||
namespace Frontend {
|
||||
class EmuWindow;
|
||||
}
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
enum class UniformBindings : u32 {
|
||||
Common = 0,
|
||||
VertexShader = 1,
|
||||
GeometryShader = 2
|
||||
};
|
||||
|
||||
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<BoolAligned, 16> bools;
|
||||
alignas(16) std::array<glm::uvec4, 4> i;
|
||||
alignas(16) std::array<glm::vec4, 96> 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 OpenGL spec");
|
||||
|
||||
struct ScreenInfo;
|
||||
class CommandScheduler;
|
||||
|
||||
class RasterizerVulkan : public VideoCore::RasterizerInterface {
|
||||
public:
|
||||
explicit RasterizerVulkan(CommandScheduler& scheduler, Frontend::EmuWindow& emu_window);
|
||||
~RasterizerVulkan() override;
|
||||
|
||||
void LoadDiskResources(const std::atomic_bool& stop_loading,
|
||||
const VideoCore::DiskResourceLoadCallback& callback) override;
|
||||
|
||||
void AddTriangle(const Pica::Shader::OutputVertex& v0, const Pica::Shader::OutputVertex& v1,
|
||||
const Pica::Shader::OutputVertex& v2) override;
|
||||
void DrawTriangles() override;
|
||||
void NotifyPicaRegisterChanged(u32 id) override;
|
||||
void FlushAll() override;
|
||||
void FlushRegion(PAddr addr, u32 size) override;
|
||||
void InvalidateRegion(PAddr addr, u32 size) override;
|
||||
void FlushAndInvalidateRegion(PAddr addr, u32 size) override;
|
||||
void ClearAll(bool flush) override;
|
||||
bool AccelerateDisplayTransfer(const GPU::Regs::DisplayTransferConfig& config) override;
|
||||
bool AccelerateTextureCopy(const GPU::Regs::DisplayTransferConfig& config) override;
|
||||
bool AccelerateFill(const GPU::Regs::MemoryFillConfig& config) override;
|
||||
bool AccelerateDisplay(const GPU::Regs::FramebufferConfig& config, PAddr framebuffer_addr,
|
||||
u32 pixel_stride, Vulkan::ScreenInfo& screen_info) override;
|
||||
bool AccelerateDrawBatch(bool is_indexed) override { return false; }
|
||||
|
||||
/// Syncs entire status to match PICA registers
|
||||
void SyncEntireState() override;
|
||||
|
||||
private:
|
||||
/// Syncs the clip enabled status to match the PICA register
|
||||
void SyncClipEnabled();
|
||||
|
||||
/// Syncs the clip coefficients to match the PICA register
|
||||
void SyncClipCoef();
|
||||
|
||||
/// Sets the OpenGL shader in accordance with the current PICA register state
|
||||
void SetShader();
|
||||
|
||||
/// Syncs the cull mode to match the PICA register
|
||||
void SyncCullMode();
|
||||
|
||||
/// Syncs the depth scale to match the PICA register
|
||||
void SyncDepthScale();
|
||||
|
||||
/// Syncs the depth offset to match the PICA register
|
||||
void SyncDepthOffset();
|
||||
|
||||
/// Syncs the blend enabled status to match the PICA register
|
||||
void SyncBlendEnabled();
|
||||
|
||||
/// Syncs the blend functions to match the PICA register
|
||||
void SyncBlendFuncs();
|
||||
|
||||
/// Syncs the blend color to match the PICA register
|
||||
void SyncBlendColor();
|
||||
|
||||
/// Syncs the fog states to match the PICA register
|
||||
void SyncFogColor();
|
||||
|
||||
/// Sync the procedural texture noise configuration to match the PICA register
|
||||
void SyncProcTexNoise();
|
||||
|
||||
/// Sync the procedural texture bias configuration to match the PICA register
|
||||
void SyncProcTexBias();
|
||||
|
||||
/// Syncs the alpha test states to match the PICA register
|
||||
void SyncAlphaTest();
|
||||
|
||||
/// Syncs the logic op states to match the PICA register
|
||||
void SyncLogicOp();
|
||||
|
||||
/// Syncs the color write mask to match the PICA register state
|
||||
void SyncColorWriteMask();
|
||||
|
||||
/// Syncs the stencil write mask to match the PICA register state
|
||||
void SyncStencilWriteMask();
|
||||
|
||||
/// Syncs the depth write mask to match the PICA register state
|
||||
void SyncDepthWriteMask();
|
||||
|
||||
/// Syncs the stencil test states to match the PICA register
|
||||
void SyncStencilTest();
|
||||
|
||||
/// Syncs the depth test states to match the PICA register
|
||||
void SyncDepthTest();
|
||||
|
||||
/// Syncs the TEV combiner color buffer to match the PICA register
|
||||
void SyncCombinerColor();
|
||||
|
||||
/// Syncs the TEV constant color to match the PICA register
|
||||
void SyncTevConstColor(std::size_t tev_index,
|
||||
const Pica::TexturingRegs::TevStageConfig& tev_stage);
|
||||
|
||||
/// Syncs the lighting global ambient color to match the PICA register
|
||||
void SyncGlobalAmbient();
|
||||
|
||||
/// Syncs the specified light's specular 0 color to match the PICA register
|
||||
void SyncLightSpecular0(int light_index);
|
||||
|
||||
/// Syncs the specified light's specular 1 color to match the PICA register
|
||||
void SyncLightSpecular1(int light_index);
|
||||
|
||||
/// Syncs the specified light's diffuse color to match the PICA register
|
||||
void SyncLightDiffuse(int light_index);
|
||||
|
||||
/// Syncs the specified light's ambient color to match the PICA register
|
||||
void SyncLightAmbient(int light_index);
|
||||
|
||||
/// Syncs the specified light's position to match the PICA register
|
||||
void SyncLightPosition(int light_index);
|
||||
|
||||
/// Syncs the specified spot light direcition to match the PICA register
|
||||
void SyncLightSpotDirection(int light_index);
|
||||
|
||||
/// Syncs the specified light's distance attenuation bias to match the PICA register
|
||||
void SyncLightDistanceAttenuationBias(int light_index);
|
||||
|
||||
/// Syncs the specified light's distance attenuation scale to match the PICA register
|
||||
void SyncLightDistanceAttenuationScale(int light_index);
|
||||
|
||||
/// Syncs the shadow rendering bias to match the PICA register
|
||||
void SyncShadowBias();
|
||||
|
||||
/// Syncs the shadow texture bias to match the PICA register
|
||||
void SyncShadowTextureBias();
|
||||
|
||||
/// Syncs and uploads the lighting, fog and proctex LUTs
|
||||
void SyncAndUploadLUTs();
|
||||
void SyncAndUploadLUTsLF();
|
||||
|
||||
/// Upload the uniform blocks to the uniform buffer object
|
||||
void UploadUniforms(bool accelerate_draw);
|
||||
|
||||
/// Generic draw function for DrawTriangles and AccelerateDrawBatch
|
||||
bool Draw(bool accelerate, bool is_indexed);
|
||||
|
||||
struct VertexArrayInfo {
|
||||
u32 vs_input_index_min;
|
||||
u32 vs_input_index_max;
|
||||
u32 vs_input_size;
|
||||
};
|
||||
|
||||
private:
|
||||
CommandScheduler& scheduler;
|
||||
RasterizerCacheVulkan res_cache;
|
||||
std::vector<HardwareVertex> vertex_batch;
|
||||
bool shader_dirty = true;
|
||||
|
||||
struct {
|
||||
UniformData data;
|
||||
std::array<bool, Pica::LightingRegs::NumLightingSampler> lighting_lut_dirty;
|
||||
bool lighting_lut_dirty_any;
|
||||
bool fog_lut_dirty;
|
||||
bool proctex_noise_lut_dirty;
|
||||
bool proctex_color_map_dirty;
|
||||
bool proctex_alpha_map_dirty;
|
||||
bool proctex_lut_dirty;
|
||||
bool proctex_diff_lut_dirty;
|
||||
bool dirty;
|
||||
} uniform_block_data = {};
|
||||
|
||||
StreamBuffer vertex_buffer, index_buffer;
|
||||
StreamBuffer uniform_buffer, texture_buffer_lut_lf, texture_buffer_lut;
|
||||
|
||||
u32 uniform_buffer_alignment;
|
||||
u32 uniform_size_aligned_vs, uniform_size_aligned_fs;
|
||||
|
||||
std::array<std::array<glm::vec2, 256>,
|
||||
Pica::LightingRegs::NumLightingSampler> lighting_lut_data{};
|
||||
std::array<glm::vec2, 128> fog_lut_data{};
|
||||
std::array<glm::vec2, 128> proctex_noise_lut_data{};
|
||||
std::array<glm::vec2, 128> proctex_color_map_data{};
|
||||
std::array<glm::vec2, 128> proctex_alpha_map_data{};
|
||||
std::array<glm::vec4, 256> proctex_lut_data{};
|
||||
std::array<glm::vec4, 256> proctex_diff_lut_data{};
|
||||
|
||||
bool allow_shadow{};
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
File diff suppressed because it is too large
Load Diff
@@ -1,346 +0,0 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <tuple>
|
||||
#include <boost/icl/interval_map.hpp>
|
||||
#include <boost/icl/interval_set.hpp>
|
||||
#include <unordered_map>
|
||||
#include <boost/functional/hash.hpp>
|
||||
#include <robin_hood.h>
|
||||
#include "common/assert.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "core/custom_tex_cache.h"
|
||||
#include "video_core/renderer_vulkan/vk_surface_params.h"
|
||||
#include "video_core/renderer_vulkan/vk_texture.h"
|
||||
#include "video_core/texture/texture_decode.h"
|
||||
|
||||
// Can be changed later here
|
||||
template <typename Key, typename T, typename Hash = typename Key::Hash>
|
||||
using HashMap = robin_hood::unordered_flat_map<Key, T, Hash>;
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
class RasterizerCacheVulkan;
|
||||
class TextureFilterer;
|
||||
class FormatReinterpreterVulkan;
|
||||
|
||||
vk::Format GetFormatTuple(SurfaceParams::PixelFormat pixel_format);
|
||||
|
||||
struct HostTextureTag {
|
||||
vk::Format format = vk::Format::eUndefined;
|
||||
u32 width = 0, height = 0;
|
||||
|
||||
// Enable comparisons
|
||||
auto operator<=>(const HostTextureTag& other) const = default;
|
||||
};
|
||||
|
||||
struct TextureCubeConfig {
|
||||
PAddr px = 0;
|
||||
PAddr nx = 0;
|
||||
PAddr py = 0;
|
||||
PAddr ny = 0;
|
||||
PAddr pz = 0;
|
||||
PAddr nz = 0;
|
||||
u32 width = 0;
|
||||
Pica::TexturingRegs::TextureFormat format;
|
||||
|
||||
// Enable comparisons
|
||||
auto operator<=>(const TextureCubeConfig& other) const = default;
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
|
||||
namespace std {
|
||||
template <>
|
||||
struct hash<Vulkan::HostTextureTag> {
|
||||
std::size_t operator()(const Vulkan::HostTextureTag& tag) const noexcept {
|
||||
std::size_t hash = 0;
|
||||
boost::hash_combine(hash, tag.format);
|
||||
boost::hash_combine(hash, tag.width);
|
||||
boost::hash_combine(hash, tag.height);
|
||||
return hash;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct hash<Vulkan::TextureCubeConfig> {
|
||||
std::size_t operator()(const Vulkan::TextureCubeConfig& config) const noexcept {
|
||||
std::size_t hash = 0;
|
||||
boost::hash_combine(hash, config.px);
|
||||
boost::hash_combine(hash, config.nx);
|
||||
boost::hash_combine(hash, config.py);
|
||||
boost::hash_combine(hash, config.ny);
|
||||
boost::hash_combine(hash, config.pz);
|
||||
boost::hash_combine(hash, config.nz);
|
||||
boost::hash_combine(hash, config.width);
|
||||
boost::hash_combine(hash, static_cast<u32>(config.format));
|
||||
return hash;
|
||||
}
|
||||
};
|
||||
} // namespace std
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
using SurfaceSet = std::set<Surface>;
|
||||
|
||||
using SurfaceRegions = boost::icl::interval_set<PAddr, std::less, SurfaceInterval>;
|
||||
using SurfaceMap =
|
||||
boost::icl::interval_map<PAddr, Surface, boost::icl::partial_absorber, std::less,
|
||||
boost::icl::inplace_plus, boost::icl::inter_section, SurfaceInterval>;
|
||||
|
||||
using SurfaceCache =
|
||||
boost::icl::interval_map<PAddr, SurfaceSet, boost::icl::partial_absorber, std::less,
|
||||
boost::icl::inplace_plus, boost::icl::inter_section, SurfaceInterval>;
|
||||
|
||||
static_assert(std::is_same<SurfaceRegions::interval_type, SurfaceCache::interval_type>() &&
|
||||
std::is_same<SurfaceMap::interval_type, SurfaceCache::interval_type>(),
|
||||
"incorrect interval types");
|
||||
|
||||
using SurfaceRect_Tuple = std::tuple<Surface, Common::Rectangle<u32>>;
|
||||
using SurfaceSurfaceRect_Tuple = std::tuple<Surface, Surface, Common::Rectangle<u32>>;
|
||||
|
||||
enum class ScaleMatch {
|
||||
Exact, // only accept same res scale
|
||||
Upscale, // only allow higher scale than params
|
||||
Ignore // accept every scaled res
|
||||
};
|
||||
|
||||
/**
|
||||
* A watcher that notifies whether a cached surface has been changed. This is useful for caching
|
||||
* surface collection objects, including texture cube and mipmap.
|
||||
*/
|
||||
struct SurfaceWatcher {
|
||||
public:
|
||||
explicit SurfaceWatcher(std::weak_ptr<CachedSurface>&& surface) : surface(std::move(surface)) {}
|
||||
|
||||
/**
|
||||
* Checks whether the surface has been changed.
|
||||
* @return false if the surface content has been changed since last Validate() call or has been
|
||||
* destroyed; otherwise true
|
||||
*/
|
||||
bool IsValid() const {
|
||||
return !surface.expired() && valid;
|
||||
}
|
||||
|
||||
/// Marks that the content of the referencing surface has been updated to the watcher user.
|
||||
void Validate() {
|
||||
ASSERT(!surface.expired());
|
||||
valid = true;
|
||||
}
|
||||
|
||||
/// Gets the referencing surface. Returns null if the surface has been destroyed
|
||||
Surface Get() const {
|
||||
return surface.lock();
|
||||
}
|
||||
|
||||
private:
|
||||
friend struct CachedSurface;
|
||||
std::weak_ptr<CachedSurface> surface;
|
||||
bool valid = false;
|
||||
};
|
||||
|
||||
class RasterizerCacheVulkan;
|
||||
|
||||
struct CachedSurface : SurfaceParams, std::enable_shared_from_this<CachedSurface> {
|
||||
CachedSurface(RasterizerCacheVulkan& owner) : owner{owner} {}
|
||||
~CachedSurface();
|
||||
|
||||
bool CanFill(const SurfaceParams& dest_surface, SurfaceInterval fill_interval) const;
|
||||
bool CanCopy(const SurfaceParams& dest_surface, SurfaceInterval copy_interval) const;
|
||||
|
||||
bool IsRegionValid(SurfaceInterval interval) const {
|
||||
return (invalid_regions.find(interval) == invalid_regions.end());
|
||||
}
|
||||
|
||||
bool IsSurfaceFullyInvalid() const {
|
||||
auto interval = GetInterval();
|
||||
return *invalid_regions.equal_range(interval).first == interval;
|
||||
}
|
||||
|
||||
bool registered = false;
|
||||
SurfaceRegions invalid_regions;
|
||||
|
||||
u32 fill_size = 0; /// Number of bytes to read from fill_data
|
||||
std::array<u8, 4> fill_data;
|
||||
|
||||
Texture texture;
|
||||
|
||||
/// max mipmap level that has been attached to the texture
|
||||
u32 max_level = 0;
|
||||
/// level_watchers[i] watches the (i+1)-th level mipmap source surface
|
||||
std::array<std::shared_ptr<SurfaceWatcher>, 7> level_watchers;
|
||||
|
||||
bool is_custom = false;
|
||||
Core::CustomTexInfo custom_tex_info;
|
||||
|
||||
static constexpr unsigned int GetBytesPerPixel(PixelFormat format) {
|
||||
return format == PixelFormat::Invalid
|
||||
? 0
|
||||
: (format == PixelFormat::D24 || GetFormatType(format) == SurfaceType::Texture)
|
||||
? 4
|
||||
: SurfaceParams::GetFormatBpp(format) / 8;
|
||||
}
|
||||
|
||||
std::vector<u8> vk_buffer;
|
||||
|
||||
// Read/Write data in 3DS memory to/from gl_buffer
|
||||
void LoadGPUBuffer(PAddr load_start, PAddr load_end);
|
||||
void FlushGPUBuffer(PAddr flush_start, PAddr flush_end);
|
||||
|
||||
// Upload/Download data in vk_buffer in/to this surface's texture
|
||||
void UploadGPUTexture(Common::Rectangle<u32> rect);
|
||||
void DownloadGPUTexture(const Common::Rectangle<u32>& rect);
|
||||
|
||||
std::shared_ptr<SurfaceWatcher> CreateWatcher() {
|
||||
auto watcher = std::make_shared<SurfaceWatcher>(weak_from_this());
|
||||
watchers.push_front(watcher);
|
||||
return watcher;
|
||||
}
|
||||
|
||||
void InvalidateAllWatcher() {
|
||||
for (const auto& watcher : watchers) {
|
||||
if (auto locked = watcher.lock()) {
|
||||
locked->valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UnlinkAllWatcher() {
|
||||
for (const auto& watcher : watchers) {
|
||||
if (auto locked = watcher.lock()) {
|
||||
locked->valid = false;
|
||||
locked->surface.reset();
|
||||
}
|
||||
}
|
||||
watchers.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
RasterizerCacheVulkan& owner;
|
||||
std::list<std::weak_ptr<SurfaceWatcher>> watchers;
|
||||
};
|
||||
|
||||
struct CachedTextureCube {
|
||||
Texture texture;
|
||||
u16 res_scale = 1;
|
||||
std::shared_ptr<SurfaceWatcher> px;
|
||||
std::shared_ptr<SurfaceWatcher> nx;
|
||||
std::shared_ptr<SurfaceWatcher> py;
|
||||
std::shared_ptr<SurfaceWatcher> ny;
|
||||
std::shared_ptr<SurfaceWatcher> pz;
|
||||
std::shared_ptr<SurfaceWatcher> nz;
|
||||
};
|
||||
|
||||
class TextureDownloader;
|
||||
|
||||
class RasterizerCacheVulkan : NonCopyable {
|
||||
public:
|
||||
RasterizerCacheVulkan();
|
||||
~RasterizerCacheVulkan();
|
||||
|
||||
/// Blit one surface's texture to another
|
||||
bool BlitSurfaces(const Surface& src_surface, const Common::Rectangle<u32>& src_rect,
|
||||
const Surface& dst_surface, const Common::Rectangle<u32>& dst_rect);
|
||||
|
||||
/// Copy one surface's region to another
|
||||
void CopySurface(const Surface& src_surface, const Surface& dst_surface,
|
||||
SurfaceInterval copy_interval);
|
||||
|
||||
/// Load a texture from 3DS memory to OpenGL and cache it (if not already cached)
|
||||
Surface GetSurface(const SurfaceParams& params, ScaleMatch match_res_scale,
|
||||
bool load_if_create);
|
||||
|
||||
/// Attempt to find a subrect (resolution scaled) of a surface, otherwise loads a texture from
|
||||
/// 3DS memory to OpenGL and caches it (if not already cached)
|
||||
SurfaceRect_Tuple GetSurfaceSubRect(const SurfaceParams& params, ScaleMatch match_res_scale,
|
||||
bool load_if_create, bool framebuffer = false);
|
||||
|
||||
/// Get a surface based on the texture configuration
|
||||
Surface GetTextureSurface(const Pica::TexturingRegs::FullTextureConfig& config);
|
||||
Surface GetTextureSurface(const Pica::Texture::TextureInfo& info, u32 max_level = 0);
|
||||
|
||||
/// Get the color and depth surfaces based on the framebuffer configuration
|
||||
SurfaceSurfaceRect_Tuple GetFramebufferSurfaces(bool using_color_fb, bool using_depth_fb,
|
||||
const Common::Rectangle<s32>& viewport_rect);
|
||||
|
||||
/// Get a surface that matches the fill config
|
||||
Surface GetFillSurface(const GPU::Regs::MemoryFillConfig& config);
|
||||
|
||||
/// Get a surface that matches a "texture copy" display transfer config
|
||||
SurfaceRect_Tuple GetTexCopySurface(const SurfaceParams& params);
|
||||
|
||||
/// Write any cached resources overlapping the region back to memory (if dirty)
|
||||
void FlushRegion(PAddr addr, u32 size, Surface flush_surface = nullptr);
|
||||
|
||||
/// Mark region as being invalidated by region_owner (nullptr if 3DS memory)
|
||||
void InvalidateRegion(PAddr addr, u32 size, const Surface& region_owner);
|
||||
|
||||
/// Flush all cached resources tracked by this cache manager
|
||||
void FlushAll();
|
||||
|
||||
/// Clear all cached resources tracked by this cache manager
|
||||
void ClearAll(bool flush);
|
||||
|
||||
// Textures from destroyed surfaces are stored here to be recyled to reduce allocation overhead
|
||||
// in the driver
|
||||
// this must be placed above the surface_cache to ensure all cached surfaces are destroyed
|
||||
// before destroying the recycler
|
||||
std::unordered_multimap<HostTextureTag, Texture> host_texture_recycler;
|
||||
|
||||
private:
|
||||
void DuplicateSurface(const Surface& src_surface, const Surface& dest_surface);
|
||||
|
||||
/// Update surface's texture for given region when necessary
|
||||
void ValidateSurface(const Surface& surface, PAddr addr, u32 size);
|
||||
|
||||
// Returns false if there is a surface in the cache at the interval with the same bit-width,
|
||||
bool NoUnimplementedReinterpretations(const Surface& surface,
|
||||
SurfaceParams& params,
|
||||
const SurfaceInterval& interval);
|
||||
|
||||
// Return true if a surface with an invalid pixel format exists at the interval
|
||||
bool IntervalHasInvalidPixelFormat(SurfaceParams& params, const SurfaceInterval& interval);
|
||||
|
||||
// Attempt to find a reinterpretable surface in the cache and use it to copy for validation
|
||||
bool ValidateByReinterpretation(const Surface& surface, SurfaceParams& params,
|
||||
const SurfaceInterval& interval);
|
||||
|
||||
/// Create a new surface
|
||||
Surface CreateSurface(const SurfaceParams& params, bool framebuffer = false);
|
||||
|
||||
/// Register surface into the cache
|
||||
void RegisterSurface(const Surface& surface);
|
||||
|
||||
/// Remove surface from the cache
|
||||
void UnregisterSurface(const Surface& surface);
|
||||
|
||||
/// Increase/decrease the number of surface in pages touching the specified region
|
||||
void UpdatePagesCachedCount(PAddr addr, u32 size, int delta);
|
||||
|
||||
SurfaceCache surface_cache;
|
||||
boost::icl::interval_map<u32, int> cached_pages;
|
||||
SurfaceMap dirty_regions;
|
||||
SurfaceSet remove_surfaces;
|
||||
|
||||
u16 resolution_scale_factor;
|
||||
|
||||
// Texture cube cache
|
||||
std::unordered_map<TextureCubeConfig, CachedTextureCube> texture_cube_cache;
|
||||
|
||||
std::recursive_mutex mutex;
|
||||
|
||||
public:
|
||||
void AllocateTexture(Texture& target, SurfaceParams::SurfaceType type, vk::Format format,
|
||||
u32 width, u32 height, bool framebuffer);
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
@@ -66,13 +66,11 @@ layout (set = 0, binding = 0) uniform shader_data {
|
||||
};
|
||||
)";
|
||||
|
||||
static std::string GetVertexInterfaceDeclaration(bool is_output, bool separable_shader) {
|
||||
static std::string GetVertexInterfaceDeclaration(bool is_output) {
|
||||
std::string out;
|
||||
|
||||
const auto append_variable = [&](std::string_view var, int location) {
|
||||
if (separable_shader) {
|
||||
out += fmt::format("layout(location = {}) ", location);
|
||||
}
|
||||
out += fmt::format("layout(location = {}) ", location);
|
||||
out += fmt::format("{}{};\n", is_output ? "out " : "in ", var);
|
||||
};
|
||||
|
||||
@@ -84,7 +82,7 @@ static std::string GetVertexInterfaceDeclaration(bool is_output, bool separable_
|
||||
append_variable("vec4 normquat", ATTRIBUTE_NORMQUAT);
|
||||
append_variable("vec3 view", ATTRIBUTE_VIEW);
|
||||
|
||||
if (is_output && separable_shader) {
|
||||
if (is_output) {
|
||||
// gl_PerVertex redeclaration is required for separate shader object
|
||||
out += R"(
|
||||
out gl_PerVertex {
|
||||
@@ -1027,7 +1025,7 @@ float ProcTexNoiseCoef(vec2 x) {
|
||||
}
|
||||
}
|
||||
|
||||
std::string ShaderGenerator::GenerateFragmentShader(const PicaFSConfig& config, bool seperable_shader) {
|
||||
std::string ShaderGenerator::GenerateFragmentShader(const PicaFSConfig& config) {
|
||||
const auto& state = config;
|
||||
std::string out;
|
||||
|
||||
@@ -1037,7 +1035,7 @@ std::string ShaderGenerator::GenerateFragmentShader(const PicaFSConfig& config,
|
||||
)";
|
||||
out += "#extension GL_ARB_separate_shader_objects : enable\n";
|
||||
|
||||
out += GetVertexInterfaceDeclaration(false, true);
|
||||
out += GetVertexInterfaceDeclaration(false);
|
||||
|
||||
out += R"(
|
||||
in vec4 gl_FragCoord;
|
||||
@@ -1379,7 +1377,7 @@ do {
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string ShaderGenerator::GenerateTrivialVertexShader(bool separable_shader) {
|
||||
std::string ShaderGenerator::GenerateTrivialVertexShader() {
|
||||
std::string out;
|
||||
out += "#version 450\n";
|
||||
out += "#extension GL_ARB_separate_shader_objects : enable\n";
|
||||
@@ -1394,7 +1392,7 @@ std::string ShaderGenerator::GenerateTrivialVertexShader(bool separable_shader)
|
||||
ATTRIBUTE_POSITION, ATTRIBUTE_COLOR, ATTRIBUTE_TEXCOORD0, ATTRIBUTE_TEXCOORD1,
|
||||
ATTRIBUTE_TEXCOORD2, ATTRIBUTE_TEXCOORD0_W, ATTRIBUTE_NORMQUAT, ATTRIBUTE_VIEW);
|
||||
|
||||
out += GetVertexInterfaceDeclaration(true, separable_shader);
|
||||
out += GetVertexInterfaceDeclaration(true);
|
||||
|
||||
out += UniformBlockDef;
|
||||
|
||||
@@ -1419,13 +1417,12 @@ void main() {
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string ShaderGenerator::GenerateVertexShader(const Pica::Shader::ShaderSetup& setup, const PicaVSConfig& config,
|
||||
bool separable_shader) {
|
||||
std::string ShaderGenerator::GenerateVertexShader(const Pica::Shader::ShaderSetup& setup, const PicaVSConfig& config) {
|
||||
LOG_CRITICAL(Render_Vulkan, "Unimplemented!");
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
std::string GenerateFixedGeometryShader(const PicaFixedGSConfig& config, bool separable_shader) {
|
||||
std::string GenerateFixedGeometryShader(const PicaFixedGSConfig& config) {
|
||||
LOG_CRITICAL(Render_Vulkan, "Unimplemented!");
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
@@ -13,14 +13,10 @@ public:
|
||||
ShaderGenerator() = default;
|
||||
~ShaderGenerator() override = default;
|
||||
|
||||
std::string GenerateTrivialVertexShader(bool separable_shader) override;
|
||||
|
||||
std::string GenerateVertexShader(const Pica::Shader::ShaderSetup& setup, const PicaVSConfig& config,
|
||||
bool separable_shader) override;
|
||||
|
||||
std::string GenerateFixedGeometryShader(const PicaFixedGSConfig& config, bool separable_shader) override;
|
||||
|
||||
std::string GenerateFragmentShader(const PicaFSConfig& config, bool separable_shader) override;
|
||||
std::string GenerateTrivialVertexShader() override;
|
||||
std::string GenerateVertexShader(const Pica::Shader::ShaderSetup& setup, const PicaVSConfig& config) override;
|
||||
std::string GenerateFixedGeometryShader(const PicaFixedGSConfig& config) override;
|
||||
std::string GenerateFragmentShader(const PicaFSConfig& config) override;
|
||||
};
|
||||
|
||||
} // namespace VideoCore
|
||||
|
@@ -1,76 +0,0 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <type_traits>
|
||||
#include <glm/glm.hpp>
|
||||
#include "common/hash.h"
|
||||
#include "video_core/regs.h"
|
||||
#include "video_core/shader/shader.h"
|
||||
#include "video_core/renderer_vulkan/vk_common.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
/// Structure that the hardware rendered vertices are composed of
|
||||
struct HardwareVertex {
|
||||
HardwareVertex() = default;
|
||||
HardwareVertex(const Pica::Shader::OutputVertex& v, bool flip_quaternion) {
|
||||
position[0] = v.pos.x.ToFloat32();
|
||||
position[1] = v.pos.y.ToFloat32();
|
||||
position[2] = v.pos.z.ToFloat32();
|
||||
position[3] = v.pos.w.ToFloat32();
|
||||
color[0] = v.color.x.ToFloat32();
|
||||
color[1] = v.color.y.ToFloat32();
|
||||
color[2] = v.color.z.ToFloat32();
|
||||
color[3] = v.color.w.ToFloat32();
|
||||
tex_coord0[0] = v.tc0.x.ToFloat32();
|
||||
tex_coord0[1] = v.tc0.y.ToFloat32();
|
||||
tex_coord1[0] = v.tc1.x.ToFloat32();
|
||||
tex_coord1[1] = v.tc1.y.ToFloat32();
|
||||
tex_coord2[0] = v.tc2.x.ToFloat32();
|
||||
tex_coord2[1] = v.tc2.y.ToFloat32();
|
||||
tex_coord0_w = v.tc0_w.ToFloat32();
|
||||
normquat[0] = v.quat.x.ToFloat32();
|
||||
normquat[1] = v.quat.y.ToFloat32();
|
||||
normquat[2] = v.quat.z.ToFloat32();
|
||||
normquat[3] = v.quat.w.ToFloat32();
|
||||
view[0] = v.view.x.ToFloat32();
|
||||
view[1] = v.view.y.ToFloat32();
|
||||
view[2] = v.view.z.ToFloat32();
|
||||
|
||||
if (flip_quaternion) {
|
||||
normquat = -normquat;
|
||||
}
|
||||
}
|
||||
|
||||
glm::vec4 position;
|
||||
glm::vec4 color;
|
||||
glm::vec2 tex_coord0;
|
||||
glm::vec2 tex_coord1;
|
||||
glm::vec2 tex_coord2;
|
||||
float tex_coord0_w;
|
||||
glm::vec4 normquat;
|
||||
glm::vec3 view;
|
||||
};
|
||||
|
||||
/**
|
||||
* Vertex structure that the drawn screen rectangles are composed of.
|
||||
*/
|
||||
struct ScreenRectVertex {
|
||||
ScreenRectVertex() = default;
|
||||
ScreenRectVertex(float x, float y, float u, float v, float s) {
|
||||
position.x = x;
|
||||
position.y = y;
|
||||
tex_coord.x = u;
|
||||
tex_coord.y = v;
|
||||
tex_coord.z = s;
|
||||
}
|
||||
|
||||
glm::vec2 position;
|
||||
glm::vec3 tex_coord;
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
@@ -1,700 +0,0 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "video_core/renderer_vulkan/vk_state.h"
|
||||
#include "video_core/renderer_vulkan/vk_instance.h"
|
||||
#include "video_core/renderer_vulkan/vk_task_scheduler.h"
|
||||
#include "video_core/renderer_vulkan/vk_shader_gen.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
std::unique_ptr<VulkanState> s_vulkan_state;
|
||||
|
||||
auto IsStencil = [](vk::Format format) -> bool {
|
||||
switch (format) {
|
||||
case vk::Format::eD16UnormS8Uint:
|
||||
case vk::Format::eD24UnormS8Uint:
|
||||
case vk::Format::eD32SfloatS8Uint:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
};
|
||||
};
|
||||
|
||||
void DescriptorUpdater::Reset() {
|
||||
write_count = 0;
|
||||
buffer_count = 0;
|
||||
image_count = 0;
|
||||
}
|
||||
|
||||
void DescriptorUpdater::Update() {
|
||||
assert(write_count > 0);
|
||||
|
||||
auto device = g_vk_instace->GetDevice();
|
||||
device.updateDescriptorSets(write_count, writes.data(), 0, nullptr);
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
void DescriptorUpdater::PushTextureArrayUpdate(vk::DescriptorSet set, u32 binding, vk::Sampler sampler,
|
||||
std::span<vk::ImageView> views) {
|
||||
ASSERT(image_count < MAX_UPDATES);
|
||||
|
||||
u32 start = image_count;
|
||||
for (auto& view : views) {
|
||||
image_infos[image_count++] = {sampler, view, vk::ImageLayout::eShaderReadOnlyOptimal};
|
||||
}
|
||||
|
||||
writes[write_count++] = vk::WriteDescriptorSet{set, binding, 0, static_cast<u32>(views.size()),
|
||||
vk::DescriptorType::eCombinedImageSampler,
|
||||
image_infos.data() + start};
|
||||
}
|
||||
|
||||
void DescriptorUpdater::PushCombinedImageSamplerUpdate(vk::DescriptorSet set, u32 binding,
|
||||
vk::Sampler sampler, vk::ImageView view) {
|
||||
ASSERT(image_count < MAX_UPDATES);
|
||||
|
||||
image_infos[image_count] = {sampler, view, vk::ImageLayout::eShaderReadOnlyOptimal};
|
||||
|
||||
writes[write_count++] = vk::WriteDescriptorSet{set, binding, 0, 1,
|
||||
vk::DescriptorType::eCombinedImageSampler,
|
||||
&image_infos[image_count++]};
|
||||
}
|
||||
|
||||
void DescriptorUpdater::PushBufferUpdate(vk::DescriptorSet set, u32 binding,
|
||||
vk::DescriptorType buffer_type, u32 offset, u32 size,
|
||||
vk::Buffer buffer, const vk::BufferView& view) {
|
||||
ASSERT(buffer_count < MAX_UPDATES);
|
||||
|
||||
buffer_infos[buffer_count] = vk::DescriptorBufferInfo{buffer, offset, size};
|
||||
|
||||
writes[write_count++] = vk::WriteDescriptorSet{set, binding, 0, 1, buffer_type, nullptr,
|
||||
&buffer_infos[buffer_count++],
|
||||
view ? &view : nullptr};
|
||||
}
|
||||
|
||||
VulkanState::VulkanState(const std::shared_ptr<Swapchain>& swapchain) : swapchain(swapchain) {
|
||||
// Create a placeholder texture which can be used in place of a real binding.
|
||||
Texture::Info info{
|
||||
.width = 1,
|
||||
.height = 1,
|
||||
.format = vk::Format::eR8G8B8A8Unorm,
|
||||
.type = vk::ImageType::e2D,
|
||||
.view_type = vk::ImageViewType::e2D,
|
||||
.usage = vk::ImageUsageFlagBits::eSampled |
|
||||
vk::ImageUsageFlagBits::eTransferDst
|
||||
};
|
||||
|
||||
placeholder.Create(info);
|
||||
|
||||
// Create texture sampler
|
||||
auto props = g_vk_instace->GetPhysicalDevice().getProperties();
|
||||
vk::SamplerCreateInfo sampler_info{
|
||||
{}, vk::Filter::eLinear,
|
||||
vk::Filter::eLinear,
|
||||
vk::SamplerMipmapMode::eLinear,
|
||||
vk::SamplerAddressMode::eClampToEdge,
|
||||
vk::SamplerAddressMode::eClampToEdge,
|
||||
vk::SamplerAddressMode::eClampToEdge,
|
||||
{}, true, props.limits.maxSamplerAnisotropy,
|
||||
false, vk::CompareOp::eAlways, {}, {},
|
||||
vk::BorderColor::eIntOpaqueBlack, false
|
||||
};
|
||||
|
||||
// TODO: Sampler cache
|
||||
auto device = g_vk_instace->GetDevice();
|
||||
render_sampler = device.createSampler(sampler_info);
|
||||
present_sampler = device.createSampler(sampler_info);
|
||||
|
||||
// Unbind all texture units
|
||||
present_view = placeholder.GetView();
|
||||
for (int i = 0; i < 4; i++) {
|
||||
render_views[i] = placeholder.GetView();
|
||||
}
|
||||
|
||||
// Configure descriptor sets and pipeline builders
|
||||
BuildDescriptorLayouts();
|
||||
ConfigureRenderPipeline();
|
||||
ConfigurePresentPipeline();
|
||||
}
|
||||
|
||||
VulkanState::~VulkanState() {
|
||||
auto device = g_vk_instace->GetDevice();
|
||||
device.waitIdle();
|
||||
|
||||
// Destroy vertex shader
|
||||
device.destroyShaderModule(render_vertex_shader);
|
||||
device.destroyShaderModule(present_vertex_shader);
|
||||
device.destroyShaderModule(present_fragment_shader);
|
||||
|
||||
// Destroy pipeline layouts
|
||||
device.destroyPipelineLayout(render_pipeline_layout);
|
||||
device.destroyPipelineLayout(present_pipeline_layout);
|
||||
|
||||
// Destroy descriptor layouts
|
||||
for (auto& layout : descriptor_layouts) {
|
||||
device.destroyDescriptorSetLayout(layout);
|
||||
}
|
||||
|
||||
// Destroy samplers
|
||||
device.destroySampler(render_sampler);
|
||||
device.destroySampler(present_sampler);
|
||||
|
||||
// Destroy shaders
|
||||
for (auto& shader : render_fragment_shaders) {
|
||||
device.destroyShaderModule(shader.second);
|
||||
}
|
||||
|
||||
// Destroy pipelines
|
||||
for (auto& pipeline : render_pipelines) {
|
||||
device.destroyPipeline(pipeline.second);
|
||||
}
|
||||
|
||||
device.destroyPipeline(present_pipeline);
|
||||
}
|
||||
|
||||
void VulkanState::Create(const std::shared_ptr<Swapchain>& swapchain) {
|
||||
if (!s_vulkan_state) {
|
||||
s_vulkan_state = std::make_unique<VulkanState>(swapchain);
|
||||
}
|
||||
}
|
||||
|
||||
VulkanState& VulkanState::Get() {
|
||||
assert(s_vulkan_state);
|
||||
return *s_vulkan_state;
|
||||
}
|
||||
|
||||
void VulkanState::SetVertexBuffer(const Buffer& buffer, vk::DeviceSize offset) {
|
||||
auto cmdbuffer = g_vk_task_scheduler->GetRenderCommandBuffer();
|
||||
cmdbuffer.bindVertexBuffers(0, buffer.GetBuffer(), offset);
|
||||
}
|
||||
|
||||
void VulkanState::SetUniformBuffer(u32 binding, u32 offset, u32 size, const Buffer& buffer) {
|
||||
auto& set = descriptor_sets[0];
|
||||
updater.PushBufferUpdate(set, binding,
|
||||
vk::DescriptorType::eUniformBuffer,
|
||||
offset, size, buffer.GetBuffer());
|
||||
descriptors_dirty = true;
|
||||
}
|
||||
|
||||
void VulkanState::SetTexture(u32 binding, const Texture& image) {
|
||||
auto& set = descriptor_sets[1];
|
||||
updater.PushCombinedImageSamplerUpdate(set, binding, render_sampler, image.GetView());
|
||||
render_views[binding] = image.GetView();
|
||||
descriptors_dirty = true;
|
||||
}
|
||||
|
||||
void VulkanState::SetTexelBuffer(u32 binding, u32 offset, u32 size, const Buffer& buffer, u32 view_index) {
|
||||
auto& set = descriptor_sets[2];
|
||||
updater.PushBufferUpdate(set, binding,
|
||||
vk::DescriptorType::eUniformTexelBuffer,
|
||||
offset, size, buffer.GetBuffer(),
|
||||
buffer.GetView(view_index));
|
||||
descriptors_dirty = true;
|
||||
}
|
||||
|
||||
void VulkanState::SetPresentTextures(vk::ImageView view0, vk::ImageView view1, vk::ImageView view2) {
|
||||
auto& set = descriptor_sets[3];
|
||||
|
||||
std::array views{view0, view1, view2};
|
||||
updater.PushTextureArrayUpdate(set, 0, present_sampler, views);
|
||||
descriptors_dirty = true;
|
||||
}
|
||||
|
||||
void VulkanState::SetPresentData(DrawInfo data) {
|
||||
auto cmdbuffer = g_vk_task_scheduler->GetRenderCommandBuffer();
|
||||
cmdbuffer.pushConstants(present_pipeline_layout, vk::ShaderStageFlagBits::eFragment |
|
||||
vk::ShaderStageFlagBits::eVertex, 0, sizeof(data), &data);
|
||||
|
||||
}
|
||||
|
||||
void VulkanState::SetPlaceholderColor(u8 red, u8 green, u8 blue, u8 alpha) {
|
||||
std::array<u8, 4> color{red, green, blue, alpha};
|
||||
placeholder.Upload(0, 0, 1, placeholder.GetArea(), color);
|
||||
}
|
||||
|
||||
void VulkanState::UnbindTexture(const Texture& image) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (render_views[i] == image.GetView()) {
|
||||
render_views[i] = placeholder.GetView();
|
||||
updater.PushCombinedImageSamplerUpdate(descriptor_sets[1], i,
|
||||
render_sampler, render_views[i]);
|
||||
descriptors_dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (present_view == image.GetView()) {
|
||||
present_view = placeholder.GetView();
|
||||
updater.PushCombinedImageSamplerUpdate(descriptor_sets[3], 0,
|
||||
render_sampler, present_view);
|
||||
descriptors_dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanState::UnbindTexture(u32 unit) {
|
||||
render_views[unit] = placeholder.GetView();
|
||||
updater.PushCombinedImageSamplerUpdate(descriptor_sets[1], unit,
|
||||
render_sampler, render_views[unit]);
|
||||
descriptors_dirty = true;
|
||||
}
|
||||
|
||||
void VulkanState::BeginRendering(Texture* color, Texture* depth, bool update_pipeline_formats,
|
||||
vk::ClearColorValue color_clear, vk::AttachmentLoadOp color_load_op,
|
||||
vk::AttachmentStoreOp color_store_op, vk::ClearDepthStencilValue depth_clear,
|
||||
vk::AttachmentLoadOp depth_load_op, vk::AttachmentStoreOp depth_store_op,
|
||||
vk::AttachmentLoadOp stencil_load_op, vk::AttachmentStoreOp stencil_store_op) {
|
||||
// Make sure to exit previous render context
|
||||
EndRendering();
|
||||
|
||||
// Make sure attachments are in optimal layout
|
||||
vk::RenderingInfo render_info{{}, {}, 1, {}};
|
||||
std::array<vk::RenderingAttachmentInfo, 3> infos{};
|
||||
|
||||
auto cmdbuffer = g_vk_task_scheduler->GetRenderCommandBuffer();
|
||||
if (color != nullptr) {
|
||||
color->Transition(cmdbuffer, vk::ImageLayout::eColorAttachmentOptimal);
|
||||
|
||||
infos[0] = vk::RenderingAttachmentInfo{
|
||||
color->GetView(), color->GetLayout(), {}, {}, {},
|
||||
color_load_op, color_store_op, color_clear
|
||||
};
|
||||
|
||||
render_info.colorAttachmentCount = 1;
|
||||
render_info.pColorAttachments = &infos[0];
|
||||
render_info.renderArea = color->GetArea();
|
||||
}
|
||||
|
||||
if (depth != nullptr) {
|
||||
depth->Transition(cmdbuffer, vk::ImageLayout::eDepthStencilAttachmentOptimal);
|
||||
|
||||
infos[1] = vk::RenderingAttachmentInfo{
|
||||
depth->GetView(), depth->GetLayout(), {}, {}, {},
|
||||
depth_load_op, depth_store_op, depth_clear
|
||||
};
|
||||
|
||||
render_info.pDepthAttachment = &infos[1];
|
||||
|
||||
|
||||
if (IsStencil(depth->GetFormat())) {
|
||||
infos[2] = vk::RenderingAttachmentInfo{
|
||||
depth->GetView(), depth->GetLayout(), {}, {}, {},
|
||||
stencil_load_op, stencil_store_op, depth_clear
|
||||
};
|
||||
|
||||
render_info.pStencilAttachment = &infos[2];
|
||||
}
|
||||
}
|
||||
|
||||
if (update_pipeline_formats) {
|
||||
render_pipeline_key.color = color != nullptr ?
|
||||
color->GetFormat() :
|
||||
vk::Format::eUndefined;
|
||||
render_pipeline_key.depth_stencil = depth != nullptr ?
|
||||
depth->GetFormat() :
|
||||
vk::Format::eUndefined;
|
||||
}
|
||||
|
||||
// Begin rendering
|
||||
cmdbuffer.beginRendering(render_info);
|
||||
rendering = true;
|
||||
}
|
||||
|
||||
void VulkanState::EndRendering() {
|
||||
if (!rendering) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto cmdbuffer = g_vk_task_scheduler->GetRenderCommandBuffer();
|
||||
cmdbuffer.endRendering();
|
||||
rendering = false;
|
||||
}
|
||||
|
||||
void VulkanState::SetViewport(vk::Viewport new_viewport) {
|
||||
if (new_viewport != viewport) {
|
||||
viewport = new_viewport;
|
||||
dirty_flags.set(DynamicStateFlags::Viewport);
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanState::SetScissor(vk::Rect2D new_scissor) {
|
||||
if (new_scissor != scissor) {
|
||||
scissor = new_scissor;
|
||||
dirty_flags.set(DynamicStateFlags::Scissor);
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanState::SetCullMode(vk::CullModeFlags flags) {
|
||||
if (cull_mode != flags) {
|
||||
cull_mode = flags;
|
||||
dirty_flags.set(DynamicStateFlags::CullMode);
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanState::SetFrontFace(vk::FrontFace face) {
|
||||
if (front_face != face) {
|
||||
front_face = face;
|
||||
dirty_flags.set(DynamicStateFlags::FrontFace);
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanState::SetColorMask(vk::ColorComponentFlags mask) {
|
||||
render_pipeline_key.blend_config.colorWriteMask = mask;
|
||||
}
|
||||
|
||||
void VulkanState::SetLogicOp(vk::LogicOp logic_op) {
|
||||
render_pipeline_key.blend_logic_op = logic_op;
|
||||
}
|
||||
|
||||
void VulkanState::SetBlendEnable(bool enable) {
|
||||
render_pipeline_key.blend_config.blendEnable = enable;
|
||||
}
|
||||
|
||||
void VulkanState::SetBlendCostants(float red, float green, float blue, float alpha) {
|
||||
std::array<float, 4> color{red, green, blue, alpha};
|
||||
if (color != blend_constants) {
|
||||
blend_constants = color;
|
||||
dirty_flags.set(DynamicStateFlags::BlendConstants);
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanState::SetBlendOp(vk::BlendOp rgb_op, vk::BlendOp alpha_op, vk::BlendFactor src_color,
|
||||
vk::BlendFactor dst_color, vk::BlendFactor src_alpha, vk::BlendFactor dst_alpha) {
|
||||
auto& blend = render_pipeline_key.blend_config;
|
||||
blend.colorBlendOp = rgb_op;
|
||||
blend.alphaBlendOp = alpha_op;
|
||||
blend.srcColorBlendFactor = src_color;
|
||||
blend.dstColorBlendFactor = dst_color;
|
||||
blend.srcAlphaBlendFactor = src_alpha;
|
||||
blend.dstAlphaBlendFactor = dst_alpha;
|
||||
}
|
||||
|
||||
void VulkanState::SetStencilWrite(u32 mask) {
|
||||
if (mask != stencil_write_mask) {
|
||||
stencil_write_mask = mask;
|
||||
dirty_flags.set(DynamicStateFlags::StencilMask);
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanState::SetStencilInput(u32 mask) {
|
||||
if (mask != stencil_input_mask) {
|
||||
stencil_input_mask = mask;
|
||||
dirty_flags.set(DynamicStateFlags::StencilMask);
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanState::SetStencilTest(bool enable, vk::StencilOp fail, vk::StencilOp pass, vk::StencilOp depth_fail,
|
||||
vk::CompareOp compare, u32 ref) {
|
||||
stencil_enabled = enable;
|
||||
stencil_ref = ref;
|
||||
fail_op = fail;
|
||||
pass_op = pass;
|
||||
depth_fail_op = depth_fail;
|
||||
stencil_op = compare;
|
||||
dirty_flags.set(DynamicStateFlags::StencilTest);
|
||||
}
|
||||
|
||||
void VulkanState::SetDepthWrite(bool enable) {
|
||||
if (enable != depth_writes) {
|
||||
depth_writes = enable;
|
||||
dirty_flags.set(DynamicStateFlags::DepthWrite);
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanState::SetDepthTest(bool enable, vk::CompareOp compare) {
|
||||
depth_enabled = enable;
|
||||
depth_op = compare;
|
||||
dirty_flags.set(DynamicStateFlags::DepthTest);
|
||||
}
|
||||
|
||||
|
||||
void VulkanState::InitDescriptorSets() {
|
||||
auto pool = g_vk_task_scheduler->GetDescriptorPool();
|
||||
auto device = g_vk_instace->GetDevice();
|
||||
|
||||
// Allocate new sets
|
||||
vk::DescriptorSetAllocateInfo allocate_info{pool, descriptor_layouts};
|
||||
auto sets = device.allocateDescriptorSets(allocate_info);
|
||||
|
||||
// Update them if the previous sets are valid
|
||||
u32 copy_count = 0;
|
||||
std::array<vk::CopyDescriptorSet, 10> copies;
|
||||
|
||||
// Copy only valid descriptors
|
||||
std::array<u32, 4> binding_count{2, 4, 3, 1};
|
||||
for (int i = 0; i < descriptor_sets.size(); i++) {
|
||||
if (descriptor_sets[i]) {
|
||||
for (u32 binding = 0; binding < binding_count[i]; binding++) {
|
||||
copies[copy_count++] = {descriptor_sets[i], binding, 0, sets[i], binding, 0, 1};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (copy_count < 10) {
|
||||
// Some descriptors weren't copied and thus need manual updating
|
||||
descriptors_dirty = true;
|
||||
}
|
||||
|
||||
device.updateDescriptorSets(0, nullptr, copy_count, copies.data());
|
||||
std::copy_n(sets.begin(), descriptor_sets.size(), descriptor_sets.begin());
|
||||
}
|
||||
|
||||
void VulkanState::ApplyRenderState(const Pica::Regs& regs) {
|
||||
// Update any pending texture units
|
||||
if (descriptors_dirty) {
|
||||
updater.Update();
|
||||
descriptors_dirty = false;
|
||||
}
|
||||
|
||||
// Bind an appropriate render pipeline
|
||||
render_pipeline_key.fragment_config = PicaFSConfig::BuildFromRegs(regs);
|
||||
auto result = render_pipelines.find(render_pipeline_key);
|
||||
|
||||
// Try to use an already complete pipeline
|
||||
vk::Pipeline pipeline;
|
||||
if (result != render_pipelines.end()) {
|
||||
pipeline = result->second;
|
||||
}
|
||||
else {
|
||||
// Maybe the shader has been compiled but the pipeline state changed?
|
||||
auto shader = render_fragment_shaders.find(render_pipeline_key.fragment_config);
|
||||
if (shader != render_fragment_shaders.end()) {
|
||||
render_pipeline_builder.SetShaderStage(vk::ShaderStageFlagBits::eFragment, shader->second);
|
||||
}
|
||||
else {
|
||||
// Re-compile shader module and create new pipeline
|
||||
auto code = GenerateFragmentShader(render_pipeline_key.fragment_config);
|
||||
auto module = CompileShader(code, vk::ShaderStageFlagBits::eFragment);
|
||||
render_fragment_shaders.emplace(render_pipeline_key.fragment_config, module);
|
||||
render_pipeline_builder.SetShaderStage(vk::ShaderStageFlagBits::eFragment, module);
|
||||
}
|
||||
|
||||
// Update pipeline builder
|
||||
auto& att = render_pipeline_key.blend_config;
|
||||
render_pipeline_builder.SetRenderingFormats(render_pipeline_key.color, render_pipeline_key.depth_stencil);
|
||||
render_pipeline_builder.SetBlendLogicOp(render_pipeline_key.blend_logic_op);
|
||||
render_pipeline_builder.SetBlendAttachment(att.blendEnable, att.srcColorBlendFactor, att.dstColorBlendFactor,
|
||||
att.colorBlendOp, att.srcAlphaBlendFactor, att.dstAlphaBlendFactor,
|
||||
att.alphaBlendOp, att.colorWriteMask);
|
||||
// Cache the resulted pipeline
|
||||
pipeline = render_pipeline_builder.Build();
|
||||
render_pipelines.emplace(render_pipeline_key, pipeline);
|
||||
}
|
||||
|
||||
// Bind the render pipeline
|
||||
auto cmdbuffer = g_vk_task_scheduler->GetRenderCommandBuffer();
|
||||
cmdbuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline);
|
||||
|
||||
// Force set all dynamic state for new pipeline
|
||||
dirty_flags.set();
|
||||
|
||||
ApplyCommonState(true);
|
||||
|
||||
// Bind render descriptor sets
|
||||
if (descriptor_sets[1]) {
|
||||
cmdbuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, render_pipeline_layout,
|
||||
0, 3, descriptor_sets.data(), 0, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_CRITICAL(Render_Vulkan, "Texture unit descriptor set unallocated!");
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
void VulkanState::ApplyPresentState() {
|
||||
// Update present texture if it was reallocated by the renderer
|
||||
if (descriptors_dirty) {
|
||||
updater.Update();
|
||||
descriptors_dirty = false;
|
||||
}
|
||||
|
||||
// Bind present pipeline and descriptors
|
||||
auto cmdbuffer = g_vk_task_scheduler->GetRenderCommandBuffer();
|
||||
cmdbuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, present_pipeline);
|
||||
|
||||
ApplyCommonState(false);
|
||||
|
||||
if (descriptor_sets[3]) {
|
||||
cmdbuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, present_pipeline_layout,
|
||||
0, 1, &descriptor_sets[3], 0, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_CRITICAL(Render_Vulkan, "Present descriptor set unallocated!");
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
void VulkanState::ApplyCommonState(bool extended) {
|
||||
// Re-apply dynamic parts of the pipeline
|
||||
auto cmdbuffer = g_vk_task_scheduler->GetRenderCommandBuffer();
|
||||
if (dirty_flags.test(DynamicStateFlags::Viewport)) {
|
||||
cmdbuffer.setViewport(0, viewport);
|
||||
}
|
||||
|
||||
if (dirty_flags.test(DynamicStateFlags::Scissor)) {
|
||||
cmdbuffer.setScissor(0, scissor);
|
||||
}
|
||||
|
||||
if (dirty_flags.test(DynamicStateFlags::DepthTest) && extended) {
|
||||
cmdbuffer.setDepthTestEnable(depth_enabled);
|
||||
cmdbuffer.setDepthCompareOp(depth_op);
|
||||
}
|
||||
|
||||
if (dirty_flags.test(DynamicStateFlags::StencilTest) && extended) {
|
||||
cmdbuffer.setStencilTestEnable(stencil_enabled);
|
||||
cmdbuffer.setStencilReference(vk::StencilFaceFlagBits::eFrontAndBack, stencil_ref);
|
||||
cmdbuffer.setStencilOp(vk::StencilFaceFlagBits::eFrontAndBack, fail_op, pass_op,
|
||||
depth_fail_op, stencil_op);
|
||||
}
|
||||
|
||||
if (dirty_flags.test(DynamicStateFlags::CullMode) && extended) {
|
||||
cmdbuffer.setCullMode(cull_mode);
|
||||
}
|
||||
|
||||
if (dirty_flags.test(DynamicStateFlags::FrontFace) && extended) {
|
||||
cmdbuffer.setFrontFace(front_face);
|
||||
}
|
||||
|
||||
if (dirty_flags.test(DynamicStateFlags::BlendConstants) && extended) {
|
||||
cmdbuffer.setBlendConstants(blend_constants.data());
|
||||
}
|
||||
|
||||
if (dirty_flags.test(DynamicStateFlags::StencilMask) && extended) {
|
||||
cmdbuffer.setStencilWriteMask(vk::StencilFaceFlagBits::eFrontAndBack, stencil_write_mask);
|
||||
cmdbuffer.setStencilCompareMask(vk::StencilFaceFlagBits::eFrontAndBack, stencil_input_mask);
|
||||
}
|
||||
|
||||
if (dirty_flags.test(DynamicStateFlags::DepthWrite) && extended) {
|
||||
cmdbuffer.setDepthWriteEnable(depth_writes);
|
||||
}
|
||||
|
||||
dirty_flags.reset();
|
||||
}
|
||||
|
||||
void VulkanState::BuildDescriptorLayouts() {
|
||||
// Render descriptor layouts
|
||||
std::array<vk::DescriptorSetLayoutBinding, 2> ubo_set{{
|
||||
{0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex |
|
||||
vk::ShaderStageFlagBits::eGeometry | vk::ShaderStageFlagBits::eFragment}, // shader_data
|
||||
{1, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex} // pica_uniforms
|
||||
}};
|
||||
std::array<vk::DescriptorSetLayoutBinding, 4> texture_set{{
|
||||
{0, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment}, // tex0
|
||||
{1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment}, // tex1
|
||||
{2, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment}, // tex2
|
||||
{3, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment}, // tex_cube
|
||||
}};
|
||||
std::array<vk::DescriptorSetLayoutBinding, 3> lut_set{{
|
||||
{0, vk::DescriptorType::eUniformTexelBuffer, 1, vk::ShaderStageFlagBits::eFragment}, // texture_buffer_lut_lf
|
||||
{1, vk::DescriptorType::eUniformTexelBuffer, 1, vk::ShaderStageFlagBits::eFragment}, // texture_buffer_lut_rg
|
||||
{2, vk::DescriptorType::eUniformTexelBuffer, 1, vk::ShaderStageFlagBits::eFragment} // texture_buffer_lut_rgba
|
||||
}};
|
||||
std::array<vk::DescriptorSetLayoutBinding, 1> present_set{{
|
||||
{0, vk::DescriptorType::eCombinedImageSampler, 3, vk::ShaderStageFlagBits::eFragment}
|
||||
}};
|
||||
|
||||
std::array<vk::DescriptorSetLayoutCreateInfo, DESCRIPTOR_SET_COUNT> create_infos{{
|
||||
{ {}, ubo_set }, { {}, texture_set }, { {}, lut_set }, { {}, present_set }
|
||||
}};
|
||||
|
||||
// Create the descriptor set layouts
|
||||
auto device = g_vk_instace->GetDevice();
|
||||
for (int i = 0; i < DESCRIPTOR_SET_COUNT; i++) {
|
||||
descriptor_layouts[i] = device.createDescriptorSetLayout(create_infos[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanState::ConfigureRenderPipeline() {
|
||||
// Make render pipeline layout
|
||||
PipelineLayoutBuilder lbuilder;
|
||||
lbuilder.AddDescriptorSet(descriptor_layouts[0]);
|
||||
lbuilder.AddDescriptorSet(descriptor_layouts[1]);
|
||||
lbuilder.AddDescriptorSet(descriptor_layouts[2]);
|
||||
render_pipeline_layout = lbuilder.Build();
|
||||
|
||||
// Set rasterization state
|
||||
render_pipeline_builder.Clear();
|
||||
render_pipeline_builder.SetPipelineLayout(render_pipeline_layout);
|
||||
render_pipeline_builder.SetPrimitiveTopology(vk::PrimitiveTopology::eTriangleList);
|
||||
render_pipeline_builder.SetLineWidth(1.0f);
|
||||
render_pipeline_builder.SetNoCullRasterizationState();
|
||||
render_pipeline_builder.SetRenderingFormats(render_pipeline_key.color, render_pipeline_key.depth_stencil);
|
||||
|
||||
// Set depth, stencil tests and blending
|
||||
render_pipeline_builder.SetNoDepthTestState();
|
||||
render_pipeline_builder.SetNoStencilState();
|
||||
render_pipeline_builder.SetBlendConstants(1.f, 1.f, 1.f, 1.f);
|
||||
render_pipeline_builder.SetBlendAttachment(true, vk::BlendFactor::eOne, vk::BlendFactor::eZero, vk::BlendOp::eAdd,
|
||||
vk::BlendFactor::eOne, vk::BlendFactor::eZero, vk::BlendOp::eAdd,
|
||||
vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG |
|
||||
vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA);
|
||||
|
||||
// Enable every required dynamic state
|
||||
std::array dynamic_states{
|
||||
vk::DynamicState::eDepthCompareOp,
|
||||
vk::DynamicState::eDepthTestEnable, vk::DynamicState::eStencilTestEnable,
|
||||
vk::DynamicState::eStencilOp,
|
||||
vk::DynamicState::eStencilCompareMask, vk::DynamicState::eStencilWriteMask,
|
||||
vk::DynamicState::eStencilReference, vk::DynamicState::eDepthWriteEnable,
|
||||
vk::DynamicState::eCullMode, vk::DynamicState::eBlendConstants,
|
||||
vk::DynamicState::eViewport, vk::DynamicState::eScissor,
|
||||
vk::DynamicState::eFrontFace
|
||||
};
|
||||
|
||||
render_pipeline_builder.SetDynamicStates(dynamic_states);
|
||||
|
||||
// Configure vertex buffer
|
||||
auto attributes = HardwareVertex::attribute_desc;
|
||||
render_pipeline_builder.AddVertexBuffer(0, sizeof(HardwareVertex), vk::VertexInputRate::eVertex, attributes);
|
||||
|
||||
// Add trivial vertex shader
|
||||
auto code = GenerateTrivialVertexShader(true);
|
||||
render_vertex_shader = CompileShader(code, vk::ShaderStageFlagBits::eVertex);
|
||||
render_pipeline_builder.SetShaderStage(vk::ShaderStageFlagBits::eVertex, render_vertex_shader);
|
||||
}
|
||||
|
||||
void VulkanState::ConfigurePresentPipeline() {
|
||||
// Make present pipeline layout
|
||||
PipelineLayoutBuilder lbuilder;
|
||||
lbuilder.AddDescriptorSet(descriptor_layouts[3]);
|
||||
lbuilder.AddPushConstants(vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment, 0, sizeof(DrawInfo));
|
||||
present_pipeline_layout = lbuilder.Build();
|
||||
|
||||
// Set rasterization state
|
||||
present_pipeline_builder.Clear();
|
||||
present_pipeline_builder.SetPipelineLayout(present_pipeline_layout);
|
||||
present_pipeline_builder.SetPrimitiveTopology(vk::PrimitiveTopology::eTriangleStrip);
|
||||
present_pipeline_builder.SetLineWidth(1.0f);
|
||||
present_pipeline_builder.SetNoCullRasterizationState();
|
||||
present_pipeline_builder.SetRenderingFormats(vk::Format::eB8G8R8A8Unorm);
|
||||
|
||||
// Set depth, stencil tests and blending
|
||||
present_pipeline_builder.SetNoDepthTestState();
|
||||
present_pipeline_builder.SetNoStencilState();
|
||||
present_pipeline_builder.SetNoBlendingState();
|
||||
|
||||
// Enable every required dynamic state
|
||||
std::array dynamic_states{
|
||||
vk::DynamicState::eViewport,
|
||||
vk::DynamicState::eScissor,
|
||||
};
|
||||
|
||||
present_pipeline_builder.SetDynamicStates(dynamic_states);
|
||||
|
||||
// Configure vertex buffer
|
||||
auto attributes = ScreenRectVertex::attribute_desc;
|
||||
present_pipeline_builder.AddVertexBuffer(0, sizeof(ScreenRectVertex), vk::VertexInputRate::eVertex, attributes);
|
||||
|
||||
// Configure shader stages
|
||||
auto vertex_code = GetPresentVertexShader();
|
||||
present_vertex_shader = CompileShader(vertex_code, vk::ShaderStageFlagBits::eVertex);
|
||||
present_pipeline_builder.SetShaderStage(vk::ShaderStageFlagBits::eVertex, present_vertex_shader);
|
||||
|
||||
auto fragment_code = GetPresentFragmentShader();
|
||||
present_fragment_shader = CompileShader(fragment_code, vk::ShaderStageFlagBits::eFragment);
|
||||
present_pipeline_builder.SetShaderStage(vk::ShaderStageFlagBits::eFragment, present_fragment_shader);
|
||||
|
||||
present_pipeline = present_pipeline_builder.Build();
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
@@ -1,180 +0,0 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <bitset>
|
||||
#include "video_core/regs.h"
|
||||
#include "video_core/renderer_vulkan/vk_buffer.h"
|
||||
#include "video_core/renderer_vulkan/vk_shader_state.h"
|
||||
#include "video_core/renderer_vulkan/vk_pipeline_builder.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
constexpr u32 DESCRIPTOR_SET_COUNT = 4;
|
||||
|
||||
struct DrawInfo {
|
||||
glm::mat4 modelview;
|
||||
glm::vec4 i_resolution;
|
||||
glm::vec4 o_resolution;
|
||||
int layer;
|
||||
};
|
||||
|
||||
class DescriptorUpdater {
|
||||
public:
|
||||
DescriptorUpdater() { Reset(); }
|
||||
~DescriptorUpdater() = default;
|
||||
|
||||
void Reset();
|
||||
void Update();
|
||||
|
||||
void PushTextureArrayUpdate(vk::DescriptorSet, u32 biding, vk::Sampler sampler,
|
||||
std::span<vk::ImageView> views);
|
||||
void PushCombinedImageSamplerUpdate(vk::DescriptorSet set, u32 binding,
|
||||
vk::Sampler sampler, vk::ImageView view);
|
||||
void PushBufferUpdate(vk::DescriptorSet set, u32 binding,
|
||||
vk::DescriptorType buffer_type, u32 offset, u32 size,
|
||||
vk::Buffer buffer, const vk::BufferView& view = VK_NULL_HANDLE);
|
||||
|
||||
private:
|
||||
static constexpr u32 MAX_DESCRIPTORS = 10;
|
||||
static constexpr u32 MAX_UPDATES = 20;
|
||||
struct Descriptor {
|
||||
vk::DescriptorImageInfo image_info;
|
||||
vk::DescriptorBufferInfo buffer_info;
|
||||
};
|
||||
|
||||
std::array<vk::WriteDescriptorSet, MAX_DESCRIPTORS> writes;
|
||||
std::array<vk::DescriptorImageInfo, MAX_UPDATES> image_infos;
|
||||
std::array<vk::DescriptorBufferInfo, MAX_UPDATES> buffer_infos;
|
||||
u32 image_count{0}, buffer_count{0}, write_count{0};
|
||||
};
|
||||
|
||||
class Swapchain;
|
||||
|
||||
/// Tracks global Vulkan state
|
||||
class VulkanState {
|
||||
public:
|
||||
VulkanState(const std::shared_ptr<Swapchain>& swapchain);
|
||||
~VulkanState();
|
||||
|
||||
/// Initialize object to its initial state
|
||||
static void Create(const std::shared_ptr<Swapchain>& swapchain);
|
||||
static VulkanState& Get();
|
||||
|
||||
/// Query state
|
||||
bool DepthTestEnabled() const { return depth_enabled && depth_writes; }
|
||||
bool StencilTestEnabled() const { return stencil_enabled && stencil_writes; }
|
||||
|
||||
/// Configure drawing state
|
||||
void SetVertexBuffer(const StreamBuffer& buffer, vk::DeviceSize offset);
|
||||
void SetViewport(vk::Viewport viewport);
|
||||
void SetScissor(vk::Rect2D scissor);
|
||||
void SetCullMode(vk::CullModeFlags flags);
|
||||
void SetFrontFace(vk::FrontFace face);
|
||||
void SetLogicOp(vk::LogicOp logic_op);
|
||||
void SetStencilWrite(u32 mask);
|
||||
void SetStencilInput(u32 mask);
|
||||
void SetStencilTest(bool enable, vk::StencilOp fail, vk::StencilOp pass, vk::StencilOp depth_fail,
|
||||
vk::CompareOp compare, u32 ref);
|
||||
void SetDepthWrite(bool enable);
|
||||
void SetDepthTest(bool enable, vk::CompareOp compare);
|
||||
void SetColorMask(vk::ColorComponentFlags mask);
|
||||
void SetBlendEnable(bool enable);
|
||||
void SetBlendCostants(float red, float green, float blue, float alpha);
|
||||
void SetBlendOp(vk::BlendOp rgb_op, vk::BlendOp alpha_op, vk::BlendFactor src_color, vk::BlendFactor dst_color,
|
||||
vk::BlendFactor src_alpha, vk::BlendFactor dst_alpha);
|
||||
|
||||
/// Rendering
|
||||
void BeginRendering(Texture* color, Texture* depth, bool update_pipeline_formats = false,
|
||||
vk::ClearColorValue color_clear = {},
|
||||
vk::AttachmentLoadOp color_load_op = vk::AttachmentLoadOp::eLoad,
|
||||
vk::AttachmentStoreOp color_store_op = vk::AttachmentStoreOp::eStore,
|
||||
vk::ClearDepthStencilValue depth_clear = {},
|
||||
vk::AttachmentLoadOp depth_load_op = vk::AttachmentLoadOp::eLoad,
|
||||
vk::AttachmentStoreOp depth_store_op = vk::AttachmentStoreOp::eStore,
|
||||
vk::AttachmentLoadOp stencil_load_op = vk::AttachmentLoadOp::eDontCare,
|
||||
vk::AttachmentStoreOp stencil_store_op = vk::AttachmentStoreOp::eDontCare);
|
||||
void EndRendering();
|
||||
|
||||
/// Configure shader resources
|
||||
void SetUniformBuffer(u32 binding, u32 offset, u32 size, const StreamBuffer& buffer);
|
||||
void SetTexture(u32 binding, const Texture& texture);
|
||||
void SetTexelBuffer(u32 binding, u32 offset, u32 size, const StreamBuffer& buffer, u32 view_index);
|
||||
void SetPresentTextures(vk::ImageView view0, vk::ImageView view1, vk::ImageView view2);
|
||||
void SetPresentData(DrawInfo data);
|
||||
void SetPlaceholderColor(u8 red, u8 green, u8 blue, u8 alpha);
|
||||
void UnbindTexture(const Texture& image);
|
||||
void UnbindTexture(u32 unit);
|
||||
|
||||
/// Apply all dirty state to the current Vulkan command buffer
|
||||
void InitDescriptorSets();
|
||||
void ApplyRenderState(const Pica::Regs& config);
|
||||
void ApplyPresentState();
|
||||
void ApplyCommonState(bool extended);
|
||||
|
||||
private:
|
||||
void BuildDescriptorLayouts();
|
||||
void ConfigureRenderPipeline();
|
||||
void ConfigurePresentPipeline();
|
||||
|
||||
private:
|
||||
// Render targets
|
||||
std::shared_ptr<Swapchain> swapchain;
|
||||
bool rendering{false};
|
||||
vk::ImageView present_view;
|
||||
std::array<vk::ImageView, 4> render_views;
|
||||
vk::Sampler render_sampler, present_sampler;
|
||||
Texture placeholder;
|
||||
|
||||
// Render state
|
||||
bool descriptors_dirty{};
|
||||
DescriptorUpdater updater;
|
||||
std::array<vk::DescriptorSetLayout, DESCRIPTOR_SET_COUNT> descriptor_layouts;
|
||||
std::array<vk::DescriptorSet, DESCRIPTOR_SET_COUNT> descriptor_sets;
|
||||
|
||||
// Pipeline caches
|
||||
PipelineCacheKey render_pipeline_key{};
|
||||
PipelineBuilder render_pipeline_builder, present_pipeline_builder;
|
||||
vk::PipelineLayout render_pipeline_layout, present_pipeline_layout;
|
||||
std::unordered_map<PipelineCacheKey, vk::Pipeline> render_pipelines;
|
||||
vk::Pipeline present_pipeline;
|
||||
|
||||
// Shader caches
|
||||
vk::ShaderModule render_vertex_shader, present_vertex_shader, present_fragment_shader;
|
||||
std::unordered_map<PicaFSConfig, vk::ShaderModule> render_fragment_shaders;
|
||||
|
||||
// Dynamic state
|
||||
enum DynamicStateFlags : u32 {
|
||||
Viewport,
|
||||
Scissor,
|
||||
LineWidth,
|
||||
DepthTest,
|
||||
DepthWrite,
|
||||
StencilTest,
|
||||
StencilMask,
|
||||
ColorWrite,
|
||||
CullMode,
|
||||
BlendConstants,
|
||||
FrontFace,
|
||||
};
|
||||
|
||||
std::bitset<16> dirty_flags;
|
||||
u32 stencil_write_mask{}, stencil_input_mask{}, stencil_ref{};
|
||||
bool depth_enabled{}, depth_writes{}, stencil_enabled{}, stencil_writes{};
|
||||
vk::StencilOp fail_op, pass_op, depth_fail_op;
|
||||
vk::CompareOp depth_op, stencil_op;
|
||||
|
||||
vk::Viewport viewport{0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f};
|
||||
vk::CullModeFlags cull_mode{};
|
||||
vk::FrontFace front_face{};
|
||||
vk::Rect2D scissor{};
|
||||
vk::LogicOp logic_op{};
|
||||
std::array<float, 4> blend_constants{};
|
||||
};
|
||||
|
||||
extern std::unique_ptr<VulkanState> s_vulkan_state;
|
||||
|
||||
} // namespace Vulkan
|
@@ -1,171 +0,0 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "video_core/renderer_vulkan/vk_rasterizer_cache.h"
|
||||
#include "video_core/renderer_vulkan/vk_surface_params.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
SurfaceParams SurfaceParams::FromInterval(SurfaceInterval interval) const {
|
||||
SurfaceParams params = *this;
|
||||
const u32 tiled_size = is_tiled ? 8 : 1;
|
||||
const u32 stride_tiled_bytes = BytesInPixels(stride * tiled_size);
|
||||
PAddr aligned_start =
|
||||
addr + Common::AlignDown(boost::icl::first(interval) - addr, stride_tiled_bytes);
|
||||
PAddr aligned_end =
|
||||
addr + Common::AlignUp(boost::icl::last_next(interval) - addr, stride_tiled_bytes);
|
||||
|
||||
if (aligned_end - aligned_start > stride_tiled_bytes) {
|
||||
params.addr = aligned_start;
|
||||
params.height = (aligned_end - aligned_start) / BytesInPixels(stride);
|
||||
} else {
|
||||
// 1 row
|
||||
ASSERT(aligned_end - aligned_start == stride_tiled_bytes);
|
||||
const u32 tiled_alignment = BytesInPixels(is_tiled ? 8 * 8 : 1);
|
||||
aligned_start =
|
||||
addr + Common::AlignDown(boost::icl::first(interval) - addr, tiled_alignment);
|
||||
aligned_end =
|
||||
addr + Common::AlignUp(boost::icl::last_next(interval) - addr, tiled_alignment);
|
||||
params.addr = aligned_start;
|
||||
params.width = PixelsInBytes(aligned_end - aligned_start) / tiled_size;
|
||||
params.stride = params.width;
|
||||
params.height = tiled_size;
|
||||
}
|
||||
params.UpdateParams();
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
SurfaceInterval SurfaceParams::GetSubRectInterval(Common::Rectangle<u32> unscaled_rect) const {
|
||||
if (unscaled_rect.GetHeight() == 0 || unscaled_rect.GetWidth() == 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (is_tiled) {
|
||||
unscaled_rect.left = Common::AlignDown(unscaled_rect.left, 8) * 8;
|
||||
unscaled_rect.bottom = Common::AlignDown(unscaled_rect.bottom, 8) / 8;
|
||||
unscaled_rect.right = Common::AlignUp(unscaled_rect.right, 8) * 8;
|
||||
unscaled_rect.top = Common::AlignUp(unscaled_rect.top, 8) / 8;
|
||||
}
|
||||
|
||||
const u32 stride_tiled = !is_tiled ? stride : stride * 8;
|
||||
|
||||
const u32 pixel_offset =
|
||||
stride_tiled * (!is_tiled ? unscaled_rect.bottom : (height / 8) - unscaled_rect.top) +
|
||||
unscaled_rect.left;
|
||||
|
||||
const u32 pixels = (unscaled_rect.GetHeight() - 1) * stride_tiled + unscaled_rect.GetWidth();
|
||||
|
||||
return {addr + BytesInPixels(pixel_offset), addr + BytesInPixels(pixel_offset + pixels)};
|
||||
}
|
||||
|
||||
SurfaceInterval SurfaceParams::GetCopyableInterval(const Surface& src_surface) const {
|
||||
SurfaceInterval result{};
|
||||
const auto valid_regions =
|
||||
SurfaceRegions(GetInterval() & src_surface->GetInterval()) - src_surface->invalid_regions;
|
||||
for (auto& valid_interval : valid_regions) {
|
||||
const SurfaceInterval aligned_interval{
|
||||
addr + Common::AlignUp(boost::icl::first(valid_interval) - addr,
|
||||
BytesInPixels(is_tiled ? 8 * 8 : 1)),
|
||||
addr + Common::AlignDown(boost::icl::last_next(valid_interval) - addr,
|
||||
BytesInPixels(is_tiled ? 8 * 8 : 1))};
|
||||
|
||||
if (BytesInPixels(is_tiled ? 8 * 8 : 1) > boost::icl::length(valid_interval) ||
|
||||
boost::icl::length(aligned_interval) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the rectangle within aligned_interval
|
||||
const u32 stride_bytes = BytesInPixels(stride) * (is_tiled ? 8 : 1);
|
||||
SurfaceInterval rect_interval{
|
||||
addr + Common::AlignUp(boost::icl::first(aligned_interval) - addr, stride_bytes),
|
||||
addr + Common::AlignDown(boost::icl::last_next(aligned_interval) - addr, stride_bytes),
|
||||
};
|
||||
if (boost::icl::first(rect_interval) > boost::icl::last_next(rect_interval)) {
|
||||
// 1 row
|
||||
rect_interval = aligned_interval;
|
||||
} else if (boost::icl::length(rect_interval) == 0) {
|
||||
// 2 rows that do not make a rectangle, return the larger one
|
||||
const SurfaceInterval row1{boost::icl::first(aligned_interval),
|
||||
boost::icl::first(rect_interval)};
|
||||
const SurfaceInterval row2{boost::icl::first(rect_interval),
|
||||
boost::icl::last_next(aligned_interval)};
|
||||
rect_interval = (boost::icl::length(row1) > boost::icl::length(row2)) ? row1 : row2;
|
||||
}
|
||||
|
||||
if (boost::icl::length(rect_interval) > boost::icl::length(result)) {
|
||||
result = rect_interval;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Common::Rectangle<u32> SurfaceParams::GetSubRect(const SurfaceParams& sub_surface) const {
|
||||
const u32 begin_pixel_index = PixelsInBytes(sub_surface.addr - addr);
|
||||
|
||||
if (is_tiled) {
|
||||
const int x0 = (begin_pixel_index % (stride * 8)) / 8;
|
||||
const int y0 = (begin_pixel_index / (stride * 8)) * 8;
|
||||
// Top to bottom
|
||||
return Common::Rectangle<u32>(x0, height - y0, x0 + sub_surface.width,
|
||||
height - (y0 + sub_surface.height));
|
||||
}
|
||||
|
||||
const int x0 = begin_pixel_index % stride;
|
||||
const int y0 = begin_pixel_index / stride;
|
||||
// Bottom to top
|
||||
return Common::Rectangle<u32>(x0, y0 + sub_surface.height, x0 + sub_surface.width, y0);
|
||||
}
|
||||
|
||||
Common::Rectangle<u32> SurfaceParams::GetScaledSubRect(const SurfaceParams& sub_surface) const {
|
||||
auto rect = GetSubRect(sub_surface);
|
||||
rect.left = rect.left * res_scale;
|
||||
rect.right = rect.right * res_scale;
|
||||
rect.top = rect.top * res_scale;
|
||||
rect.bottom = rect.bottom * res_scale;
|
||||
return rect;
|
||||
}
|
||||
|
||||
bool SurfaceParams::ExactMatch(const SurfaceParams& other_surface) const {
|
||||
return std::tie(other_surface.addr, other_surface.width, other_surface.height,
|
||||
other_surface.stride, other_surface.pixel_format, other_surface.is_tiled) ==
|
||||
std::tie(addr, width, height, stride, pixel_format, is_tiled) &&
|
||||
pixel_format != PixelFormat::Invalid;
|
||||
}
|
||||
|
||||
bool SurfaceParams::CanSubRect(const SurfaceParams& sub_surface) const {
|
||||
return sub_surface.addr >= addr && sub_surface.end <= end &&
|
||||
sub_surface.pixel_format == pixel_format && pixel_format != PixelFormat::Invalid &&
|
||||
sub_surface.is_tiled == is_tiled &&
|
||||
(sub_surface.addr - addr) % BytesInPixels(is_tiled ? 64 : 1) == 0 &&
|
||||
(sub_surface.stride == stride || sub_surface.height <= (is_tiled ? 8u : 1u)) &&
|
||||
GetSubRect(sub_surface).right <= stride;
|
||||
}
|
||||
|
||||
bool SurfaceParams::CanExpand(const SurfaceParams& expanded_surface) const {
|
||||
return pixel_format != PixelFormat::Invalid && pixel_format == expanded_surface.pixel_format &&
|
||||
addr <= expanded_surface.end && expanded_surface.addr <= end &&
|
||||
is_tiled == expanded_surface.is_tiled && stride == expanded_surface.stride &&
|
||||
(std::max(expanded_surface.addr, addr) - std::min(expanded_surface.addr, addr)) %
|
||||
BytesInPixels(stride * (is_tiled ? 8 : 1)) ==
|
||||
0;
|
||||
}
|
||||
|
||||
bool SurfaceParams::CanTexCopy(const SurfaceParams& texcopy_params) const {
|
||||
if (pixel_format == PixelFormat::Invalid || addr > texcopy_params.addr ||
|
||||
end < texcopy_params.end) {
|
||||
return false;
|
||||
}
|
||||
if (texcopy_params.width != texcopy_params.stride) {
|
||||
const u32 tile_stride = BytesInPixels(stride * (is_tiled ? 8 : 1));
|
||||
return (texcopy_params.addr - addr) % BytesInPixels(is_tiled ? 64 : 1) == 0 &&
|
||||
texcopy_params.width % BytesInPixels(is_tiled ? 64 : 1) == 0 &&
|
||||
(texcopy_params.height == 1 || texcopy_params.stride == tile_stride) &&
|
||||
((texcopy_params.addr - addr) % tile_stride) + texcopy_params.width <= tile_stride;
|
||||
}
|
||||
return FromInterval(texcopy_params.GetInterval()).GetInterval() == texcopy_params.GetInterval();
|
||||
}
|
||||
|
||||
} // namespace OpenGL
|
@@ -1,270 +0,0 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <climits>
|
||||
#include <boost/icl/interval.hpp>
|
||||
#include "common/assert.h"
|
||||
#include "common/math_util.h"
|
||||
#include "core/hw/gpu.h"
|
||||
#include "video_core/regs_framebuffer.h"
|
||||
#include "video_core/regs_texturing.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
struct CachedSurface;
|
||||
using Surface = std::shared_ptr<CachedSurface>;
|
||||
|
||||
using SurfaceInterval = boost::icl::right_open_interval<PAddr>;
|
||||
|
||||
struct SurfaceParams {
|
||||
private:
|
||||
static constexpr std::array<unsigned int, 18> BPP_TABLE = {
|
||||
32, // RGBA8
|
||||
24, // RGB8
|
||||
16, // RGB5A1
|
||||
16, // RGB565
|
||||
16, // RGBA4
|
||||
16, // IA8
|
||||
16, // RG8
|
||||
8, // I8
|
||||
8, // A8
|
||||
8, // IA4
|
||||
4, // I4
|
||||
4, // A4
|
||||
4, // ETC1
|
||||
8, // ETC1A4
|
||||
16, // D16
|
||||
0,
|
||||
24, // D24
|
||||
32, // D24S8
|
||||
};
|
||||
|
||||
public:
|
||||
enum class PixelFormat {
|
||||
// First 5 formats are shared between textures and color buffers
|
||||
RGBA8 = 0,
|
||||
RGB8 = 1,
|
||||
RGB5A1 = 2,
|
||||
RGB565 = 3,
|
||||
RGBA4 = 4,
|
||||
|
||||
// Texture-only formats
|
||||
IA8 = 5,
|
||||
RG8 = 6,
|
||||
I8 = 7,
|
||||
A8 = 8,
|
||||
IA4 = 9,
|
||||
I4 = 10,
|
||||
A4 = 11,
|
||||
ETC1 = 12,
|
||||
ETC1A4 = 13,
|
||||
|
||||
// Depth buffer-only formats
|
||||
D16 = 14,
|
||||
// gap
|
||||
D24 = 16,
|
||||
D24S8 = 17,
|
||||
|
||||
Invalid = 255,
|
||||
};
|
||||
|
||||
enum class SurfaceType {
|
||||
Color = 0,
|
||||
Texture = 1,
|
||||
Depth = 2,
|
||||
DepthStencil = 3,
|
||||
Fill = 4,
|
||||
Invalid = 5
|
||||
};
|
||||
|
||||
static constexpr unsigned int GetFormatBpp(PixelFormat format) {
|
||||
const auto format_idx = static_cast<std::size_t>(format);
|
||||
DEBUG_ASSERT_MSG(format_idx < BPP_TABLE.size(), "Invalid pixel format {}", format_idx);
|
||||
return BPP_TABLE[format_idx];
|
||||
}
|
||||
|
||||
unsigned int GetFormatBpp() const {
|
||||
return GetFormatBpp(pixel_format);
|
||||
}
|
||||
|
||||
static std::string_view PixelFormatAsString(PixelFormat format) {
|
||||
switch (format) {
|
||||
case PixelFormat::RGBA8:
|
||||
return "RGBA8";
|
||||
case PixelFormat::RGB8:
|
||||
return "RGB8";
|
||||
case PixelFormat::RGB5A1:
|
||||
return "RGB5A1";
|
||||
case PixelFormat::RGB565:
|
||||
return "RGB565";
|
||||
case PixelFormat::RGBA4:
|
||||
return "RGBA4";
|
||||
case PixelFormat::IA8:
|
||||
return "IA8";
|
||||
case PixelFormat::RG8:
|
||||
return "RG8";
|
||||
case PixelFormat::I8:
|
||||
return "I8";
|
||||
case PixelFormat::A8:
|
||||
return "A8";
|
||||
case PixelFormat::IA4:
|
||||
return "IA4";
|
||||
case PixelFormat::I4:
|
||||
return "I4";
|
||||
case PixelFormat::A4:
|
||||
return "A4";
|
||||
case PixelFormat::ETC1:
|
||||
return "ETC1";
|
||||
case PixelFormat::ETC1A4:
|
||||
return "ETC1A4";
|
||||
case PixelFormat::D16:
|
||||
return "D16";
|
||||
case PixelFormat::D24:
|
||||
return "D24";
|
||||
case PixelFormat::D24S8:
|
||||
return "D24S8";
|
||||
default:
|
||||
return "Not a real pixel format";
|
||||
}
|
||||
}
|
||||
|
||||
static PixelFormat PixelFormatFromTextureFormat(Pica::TexturingRegs::TextureFormat format) {
|
||||
return ((unsigned int)format < 14) ? (PixelFormat)format : PixelFormat::Invalid;
|
||||
}
|
||||
|
||||
static PixelFormat PixelFormatFromColorFormat(Pica::FramebufferRegs::ColorFormat format) {
|
||||
return ((unsigned int)format < 5) ? (PixelFormat)format : PixelFormat::Invalid;
|
||||
}
|
||||
|
||||
static PixelFormat PixelFormatFromDepthFormat(Pica::FramebufferRegs::DepthFormat format) {
|
||||
return ((unsigned int)format < 4) ? (PixelFormat)((unsigned int)format + 14)
|
||||
: PixelFormat::Invalid;
|
||||
}
|
||||
|
||||
static PixelFormat PixelFormatFromGPUPixelFormat(GPU::Regs::PixelFormat format) {
|
||||
switch (format) {
|
||||
// RGB565 and RGB5A1 are switched in PixelFormat compared to ColorFormat
|
||||
case GPU::Regs::PixelFormat::RGB565:
|
||||
return PixelFormat::RGB565;
|
||||
case GPU::Regs::PixelFormat::RGB5A1:
|
||||
return PixelFormat::RGB5A1;
|
||||
default:
|
||||
return ((unsigned int)format < 5) ? (PixelFormat)format : PixelFormat::Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
static bool CheckFormatsBlittable(PixelFormat pixel_format_a, PixelFormat pixel_format_b) {
|
||||
SurfaceType a_type = GetFormatType(pixel_format_a);
|
||||
SurfaceType b_type = GetFormatType(pixel_format_b);
|
||||
|
||||
if ((a_type == SurfaceType::Color || a_type == SurfaceType::Texture) &&
|
||||
(b_type == SurfaceType::Color || b_type == SurfaceType::Texture)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (a_type == SurfaceType::Depth && b_type == SurfaceType::Depth) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (a_type == SurfaceType::DepthStencil && b_type == SurfaceType::DepthStencil) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static constexpr SurfaceType GetFormatType(PixelFormat pixel_format) {
|
||||
if ((unsigned int)pixel_format < 5) {
|
||||
return SurfaceType::Color;
|
||||
}
|
||||
|
||||
if ((unsigned int)pixel_format < 14) {
|
||||
return SurfaceType::Texture;
|
||||
}
|
||||
|
||||
if (pixel_format == PixelFormat::D16 || pixel_format == PixelFormat::D24) {
|
||||
return SurfaceType::Depth;
|
||||
}
|
||||
|
||||
if (pixel_format == PixelFormat::D24S8) {
|
||||
return SurfaceType::DepthStencil;
|
||||
}
|
||||
|
||||
return SurfaceType::Invalid;
|
||||
}
|
||||
|
||||
/// Update the params "size", "end" and "type" from the already set "addr", "width", "height"
|
||||
/// and "pixel_format"
|
||||
void UpdateParams() {
|
||||
if (stride == 0) {
|
||||
stride = width;
|
||||
}
|
||||
type = GetFormatType(pixel_format);
|
||||
size = !is_tiled ? BytesInPixels(stride * (height - 1) + width)
|
||||
: BytesInPixels(stride * 8 * (height / 8 - 1) + width * 8);
|
||||
end = addr + size;
|
||||
}
|
||||
|
||||
SurfaceInterval GetInterval() const {
|
||||
return SurfaceInterval(addr, end);
|
||||
}
|
||||
|
||||
// Returns the outer rectangle containing "interval"
|
||||
SurfaceParams FromInterval(SurfaceInterval interval) const;
|
||||
|
||||
SurfaceInterval GetSubRectInterval(Common::Rectangle<u32> unscaled_rect) const;
|
||||
|
||||
// Returns the region of the biggest valid rectange within interval
|
||||
SurfaceInterval GetCopyableInterval(const Surface& src_surface) const;
|
||||
|
||||
u32 GetScaledWidth() const {
|
||||
return width * res_scale;
|
||||
}
|
||||
|
||||
u32 GetScaledHeight() const {
|
||||
return height * res_scale;
|
||||
}
|
||||
|
||||
Common::Rectangle<u32> GetRect() const {
|
||||
return {0, height, width, 0};
|
||||
}
|
||||
|
||||
Common::Rectangle<u32> GetScaledRect() const {
|
||||
return {0, GetScaledHeight(), GetScaledWidth(), 0};
|
||||
}
|
||||
|
||||
u32 PixelsInBytes(u32 size) const {
|
||||
return size * CHAR_BIT / GetFormatBpp(pixel_format);
|
||||
}
|
||||
|
||||
u32 BytesInPixels(u32 pixels) const {
|
||||
return pixels * GetFormatBpp(pixel_format) / CHAR_BIT;
|
||||
}
|
||||
|
||||
bool ExactMatch(const SurfaceParams& other_surface) const;
|
||||
bool CanSubRect(const SurfaceParams& sub_surface) const;
|
||||
bool CanExpand(const SurfaceParams& expanded_surface) const;
|
||||
bool CanTexCopy(const SurfaceParams& texcopy_params) const;
|
||||
|
||||
Common::Rectangle<u32> GetSubRect(const SurfaceParams& sub_surface) const;
|
||||
Common::Rectangle<u32> GetScaledSubRect(const SurfaceParams& sub_surface) const;
|
||||
|
||||
PAddr addr = 0;
|
||||
PAddr end = 0;
|
||||
u32 size = 0;
|
||||
|
||||
u32 width = 0;
|
||||
u32 height = 0;
|
||||
u32 stride = 0;
|
||||
u16 res_scale = 1;
|
||||
|
||||
bool is_tiled = false;
|
||||
PixelFormat pixel_format = PixelFormat::Invalid;
|
||||
SurfaceType type = SurfaceType::Invalid;
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
@@ -147,8 +147,8 @@ Texture::~Texture() {
|
||||
// Schedule deletion of the texture after it's no longer used by the GPU
|
||||
scheduler.Schedule(deleter);
|
||||
} else if (!is_texture_owned) {
|
||||
// If the texture is not owning, destroy the view immediately as
|
||||
// synchronization is the caller's responsibility
|
||||
// If the texture is not owning, destroy the view immediately.
|
||||
// Synchronization is the caller's responsibility
|
||||
vk::Device device = instance.GetDevice();
|
||||
device.destroyImageView(image_view);
|
||||
}
|
||||
@@ -261,10 +261,9 @@ void Texture::Upload(Rect2D rectangle, u32 stride, std::span<const u8> data, u32
|
||||
// If the adverised format supports blitting then use GPU accelerated
|
||||
// format conversion.
|
||||
if (internal_format != advertised_format &&
|
||||
instance.IsFormatSupported(advertised_format,
|
||||
vk::FormatFeatureFlagBits::eBlitSrc)) {
|
||||
instance.IsFormatSupported(advertised_format, vk::FormatFeatureFlagBits::eBlitSrc)) {
|
||||
// Creating a new staging texture for each upload/download is expensive
|
||||
// but this path is not common. TODO: Profile this
|
||||
// but this path should not be common. TODO: Profile this
|
||||
StagingTexture staging{instance, scheduler, info};
|
||||
|
||||
const std::array offsets = {
|
||||
@@ -274,9 +273,19 @@ void Texture::Upload(Rect2D rectangle, u32 stride, std::span<const u8> data, u32
|
||||
};
|
||||
|
||||
const vk::ImageBlit image_blit = {
|
||||
.srcSubresource = {aspect, level, 0, 1},
|
||||
.srcSubresource = {
|
||||
.aspectMask = aspect,
|
||||
.mipLevel = level,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = 1
|
||||
},
|
||||
.srcOffsets = offsets,
|
||||
.dstSubresource = {aspect, level, 0, 1},
|
||||
.dstSubresource = {
|
||||
.aspectMask = aspect,
|
||||
.mipLevel = level,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = 1
|
||||
},
|
||||
.dstOffsets = offsets
|
||||
};
|
||||
|
||||
@@ -301,7 +310,7 @@ void Texture::Upload(Rect2D rectangle, u32 stride, std::span<const u8> data, u32
|
||||
std::memcpy(slice.data(), data.data(), byte_count);
|
||||
staging.Commit(byte_count);
|
||||
|
||||
// TODO: Handle depth and stencil uploads
|
||||
// TODO: Handle format convertions and depth/stencil uploads
|
||||
ASSERT(aspect == vk::ImageAspectFlagBits::eColor &&
|
||||
advertised_format == internal_format);
|
||||
|
||||
@@ -338,10 +347,9 @@ void Texture::Download(Rect2D rectangle, u32 stride, std::span<u8> data, u32 lev
|
||||
// If the adverised format supports blitting then use GPU accelerated
|
||||
// format conversion.
|
||||
if (internal_format != advertised_format &&
|
||||
instance.IsFormatSupported(advertised_format,
|
||||
vk::FormatFeatureFlagBits::eBlitDst)) {
|
||||
instance.IsFormatSupported(advertised_format, vk::FormatFeatureFlagBits::eBlitDst)) {
|
||||
// Creating a new staging texture for each upload/download is expensive
|
||||
// but this path is not common. TODO: Profile this
|
||||
// but this path should not be common. TODO: Profile this
|
||||
StagingTexture staging{instance, scheduler, info};
|
||||
|
||||
const std::array offsets = {
|
||||
@@ -351,9 +359,19 @@ void Texture::Download(Rect2D rectangle, u32 stride, std::span<u8> data, u32 lev
|
||||
};
|
||||
|
||||
const vk::ImageBlit image_blit = {
|
||||
.srcSubresource = {aspect, level, 0, 1},
|
||||
.srcSubresource = {
|
||||
.aspectMask = aspect,
|
||||
.mipLevel = level,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = 1
|
||||
},
|
||||
.srcOffsets = offsets,
|
||||
.dstSubresource = {aspect, level, 0, 1},
|
||||
.dstSubresource = {
|
||||
.aspectMask = aspect,
|
||||
.mipLevel = level,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = 1
|
||||
},
|
||||
.dstOffsets = offsets
|
||||
};
|
||||
|
||||
@@ -376,6 +394,10 @@ void Texture::Download(Rect2D rectangle, u32 stride, std::span<u8> data, u32 lev
|
||||
Buffer& staging = scheduler.GetCommandUploadBuffer();
|
||||
const u64 staging_offset = staging.GetCurrentOffset();
|
||||
|
||||
// TODO: Handle format convertions and depth/stencil downloads
|
||||
ASSERT(aspect == vk::ImageAspectFlagBits::eColor &&
|
||||
advertised_format == internal_format);
|
||||
|
||||
const vk::BufferImageCopy copy_region = {
|
||||
.bufferOffset = staging_offset,
|
||||
.bufferRowLength = stride,
|
||||
@@ -409,6 +431,7 @@ void Texture::Download(Rect2D rectangle, u32 stride, std::span<u8> data, u32 lev
|
||||
|
||||
void Texture::BlitTo(TextureHandle dest, Rect2D source_rect, Rect2D dest_rect, u32 src_level, u32 dest_level,
|
||||
u32 src_layer, u32 dest_layer) {
|
||||
|
||||
Texture* dest_texture = static_cast<Texture*>(dest.Get());
|
||||
|
||||
// Prepare images for transfer
|
||||
@@ -449,7 +472,7 @@ void Texture::BlitTo(TextureHandle dest, Rect2D source_rect, Rect2D dest_rect, u
|
||||
dest_texture->GetHandle(), vk::ImageLayout::eTransferDstOptimal,
|
||||
blit_area, vk::Filter::eNearest);
|
||||
|
||||
// Revert changes to the layout
|
||||
// Prepare for shader reads
|
||||
Transition(command_buffer, vk::ImageLayout::eShaderReadOnlyOptimal);
|
||||
dest_texture->Transition(command_buffer, vk::ImageLayout::eShaderReadOnlyOptimal);
|
||||
}
|
||||
@@ -501,6 +524,42 @@ void Texture::GenerateMipmaps() {
|
||||
Transition(command_buffer, vk::ImageLayout::eShaderReadOnlyOptimal, 0, info.levels);
|
||||
}
|
||||
|
||||
void Texture::CopyFrom(TextureHandle source) {
|
||||
const vk::ImageCopy image_copy = {
|
||||
.srcSubresource = {
|
||||
.aspectMask = aspect,
|
||||
.mipLevel = 0,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = 1
|
||||
},
|
||||
.srcOffset = {0, 0, 0},
|
||||
.dstSubresource = {
|
||||
.aspectMask = aspect,
|
||||
.mipLevel = 0,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = 1
|
||||
},
|
||||
.dstOffset = {0, 0, 0},
|
||||
.extent = {source->GetWidth(), source->GetHeight(), 1}
|
||||
};
|
||||
|
||||
vk::CommandBuffer command_buffer = scheduler.GetRenderCommandBuffer();
|
||||
Texture* texture = static_cast<Texture*>(source.Get());
|
||||
|
||||
// Transition images
|
||||
vk::ImageLayout old_layout = texture->GetLayout();
|
||||
texture->Transition(command_buffer, vk::ImageLayout::eTransferSrcOptimal);
|
||||
Transition(command_buffer, vk::ImageLayout::eTransferDstOptimal);
|
||||
|
||||
// Perform copy
|
||||
command_buffer.copyImage(texture->GetHandle(), vk::ImageLayout::eTransferSrcOptimal,
|
||||
image, vk::ImageLayout::eTransferDstOptimal, image_copy);
|
||||
|
||||
// We need to preserve the old texture layout
|
||||
texture->Transition(command_buffer, old_layout);
|
||||
Transition(command_buffer, vk::ImageLayout::eShaderReadOnlyOptimal);
|
||||
}
|
||||
|
||||
StagingTexture::StagingTexture(Instance& instance, CommandScheduler& scheduler, const TextureInfo& info) :
|
||||
TextureBase(info), instance(instance), scheduler(scheduler) {
|
||||
|
||||
@@ -538,9 +597,8 @@ StagingTexture::StagingTexture(Instance& instance, CommandScheduler& scheduler,
|
||||
// Map memory
|
||||
mapped_ptr = alloc_info.pMappedData;
|
||||
|
||||
// Transition image to VK_IMAGE_LAYOUT_GENERAL. This layout is convenient
|
||||
// for staging textures since it allows for well defined host access and
|
||||
// works with vkCmdBlitImage, thus eliminating the need for layout transitions
|
||||
// For staging textures the most conventient layout is VK_IMAGE_LAYOUT_GENERAL because it allows
|
||||
// for well defined host access and works with vkCmdBlitImage, thus eliminating the need for layout transitions
|
||||
const vk::ImageMemoryBarrier barrier = {
|
||||
.srcAccessMask = vk::AccessFlagBits::eNone,
|
||||
.dstAccessMask = vk::AccessFlagBits::eNone,
|
||||
|
@@ -38,6 +38,8 @@ public:
|
||||
void BlitTo(TextureHandle dest, Rect2D src_rectangle, Rect2D dest_rect, u32 src_level = 0,
|
||||
u32 dest_level = 0, u32 src_layer = 0, u32 dest_layer = 0) override;
|
||||
|
||||
void CopyFrom(TextureHandle source) override;
|
||||
|
||||
void GenerateMipmaps() override;
|
||||
|
||||
/// Overrides the layout of provided image subresource
|
||||
|
Reference in New Issue
Block a user