renderer_vulkan: Port rasterizer to Vulkan

* This is a massive code dump so I'm not going to even attempt to explain what changed
This commit is contained in:
GPUCode
2022-05-13 19:16:53 +03:00
parent 1f967b4770
commit 72340c5685
20 changed files with 1534 additions and 1668 deletions

View File

@@ -13,6 +13,10 @@ namespace OpenGL {
struct ScreenInfo;
}
namespace Vulkan {
struct ScreenInfo;
}
namespace Pica::Shader {
struct OutputVertex;
} // namespace Pica::Shader
@@ -80,6 +84,13 @@ public:
return false;
}
/// Attempt to use a faster method to display the framebuffer to screen
virtual bool AccelerateDisplay(const GPU::Regs::FramebufferConfig& config,
PAddr framebuffer_addr, u32 pixel_stride,
Vulkan::ScreenInfo& screen_info) {
return false;
}
/// Attempt to draw using hardware shaders
virtual bool AccelerateDrawBatch(bool is_indexed) {
return false;

View File

@@ -21,8 +21,7 @@ namespace Frontend {
struct Frame {
u32 width = 0, height = 0;
bool color_reloaded = false;
Vulkan::VKTexture color;
Vulkan::VKFramebuffer render, present;
Vulkan::VKTexture* color;
vk::UniqueFence render_fence, present_fence;
};
} // namespace Frontend
@@ -31,9 +30,9 @@ namespace Vulkan {
/// Structure used for storing information about the display target for each 3DS screen
struct ScreenInfo {
u32 display_texture;
Vulkan::VKTexture* display_texture;
Common::Rectangle<float> display_texcoords;
VKTexture texture;
Vulkan::VKTexture* texture;
GPU::Regs::PixelFormat format;
};
@@ -86,7 +85,6 @@ private:
// OpenGL object IDs
VKBuffer vertex_buffer;
OGLProgram shader;
VKFramebuffer screenshot_framebuffer;
OGLSampler filter_sampler;
/// Display information for top and bottom screens respectively

View File

@@ -12,54 +12,45 @@
namespace Vulkan {
VKBuffer::~VKBuffer() {
if (memory != nullptr) {
g_vk_instace->GetDevice().unmapMemory(buffer_memory);
}
auto deleter = [this]() {
if (buffer) {
auto& device = g_vk_instace->GetDevice();
device.destroyBuffer(buffer);
device.freeMemory(buffer_memory);
device.destroyBufferView(buffer_view);
if (buffer) {
if (memory != nullptr) {
g_vk_instace->GetDevice().unmapMemory(buffer_memory);
}
};
g_vk_task_scheduler->Schedule(deleter);
auto deleter = [this]() {
auto& device = g_vk_instace->GetDevice();
device.destroyBuffer(buffer);
device.freeMemory(buffer_memory);
};
g_vk_task_scheduler->Schedule(deleter);
}
}
void VKBuffer::Create(u32 byte_count, vk::MemoryPropertyFlags properties, vk::BufferUsageFlags usage,
vk::Format view_format) {
void VKBuffer::Create(const VKBuffer::Info& info) {
auto& device = g_vk_instace->GetDevice();
size = byte_count;
buffer_info = info;
vk::BufferCreateInfo bufferInfo({}, byte_count, usage);
vk::BufferCreateInfo bufferInfo({}, info.size, info.usage);
buffer = device.createBuffer(bufferInfo);
auto mem_requirements = device.getBufferMemoryRequirements(buffer);
auto memory_type_index = FindMemoryType(mem_requirements.memoryTypeBits, properties);
auto memory_type_index = FindMemoryType(mem_requirements.memoryTypeBits, info.properties);
vk::MemoryAllocateInfo alloc_info(mem_requirements.size, memory_type_index);
buffer_memory = device.allocateMemory(alloc_info);
device.bindBufferMemory(buffer, buffer_memory, 0);
// Optionally map the buffer to CPU memory
if (properties & vk::MemoryPropertyFlagBits::eHostVisible) {
memory = device.mapMemory(buffer_memory, 0, byte_count);
}
// Create buffer view for texel buffers
if (usage & vk::BufferUsageFlagBits::eStorageTexelBuffer ||
usage & vk::BufferUsageFlagBits::eUniformTexelBuffer) {
vk::BufferViewCreateInfo view_info({}, buffer, view_format, 0, byte_count);
buffer_view = device.createBufferView(view_info);
if (info.properties & vk::MemoryPropertyFlagBits::eHostVisible) {
memory = device.mapMemory(buffer_memory, 0, info.size);
}
}
void VKBuffer::CopyBuffer(VKBuffer& src_buffer, VKBuffer& dst_buffer, const vk::BufferCopy& region) {
void VKBuffer::CopyBuffer(VKBuffer* src_buffer, VKBuffer* dst_buffer, vk::BufferCopy region) {
auto command_buffer = g_vk_task_scheduler->GetCommandBuffer();
command_buffer.copyBuffer(src_buffer.buffer, dst_buffer.buffer, region);
command_buffer.copyBuffer(src_buffer->buffer, dst_buffer->buffer, region);
}
u32 VKBuffer::FindMemoryType(u32 type_filter, vk::MemoryPropertyFlags properties) {
@@ -76,19 +67,4 @@ u32 VKBuffer::FindMemoryType(u32 type_filter, vk::MemoryPropertyFlags properties
UNREACHABLE();
}
void StagingBuffer::Create(u32 size) {
buffer.Create(size, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent,
vk::BufferUsageFlagBits::eTransferSrc);
}
u8* StagingBuffer::Request(u32 bytes) {
// Check if there is enough space left
if (buffer.GetSize() - end_offset >= bytes) {
u8* ptr = buffer.GetHostPointer() + end_offset;
end_offset += bytes;
// Schedule the memory to be freed
}
}
}

View File

@@ -15,29 +15,33 @@ namespace Vulkan {
/// Generic Vulkan buffer object used by almost every resource
class VKBuffer final : public NonCopyable {
public:
struct Info {
u32 size;
vk::MemoryPropertyFlags properties;
vk::BufferUsageFlags usage;
};
VKBuffer() = default;
VKBuffer(VKBuffer&&) = default;
~VKBuffer();
/// Create a generic Vulkan buffer object
void Create(u32 size, vk::MemoryPropertyFlags properties, vk::BufferUsageFlags usage,
vk::Format view_format = vk::Format::eUndefined);
/// Create a new Vulkan buffer object
void Create(const Info& info);
/// Global utility functions used by other objects
static u32 FindMemoryType(u32 type_filter, vk::MemoryPropertyFlags properties);
static void CopyBuffer(VKBuffer& src_buffer, VKBuffer& dst_buffer, const vk::BufferCopy& region);
static void CopyBuffer(VKBuffer* src_buffer, VKBuffer* dst_buffer, vk::BufferCopy region);
/// Return a pointer to the mapped memory if the buffer is host mapped
u8* GetHostPointer() { return reinterpret_cast<u8*>(memory); }
vk::Buffer& GetBuffer() { return buffer; }
u32 GetSize() const { return size; }
u32 GetSize() const { return buffer_info.size; }
private:
Info buffer_info;
void* memory = nullptr;
vk::Buffer buffer;
vk::DeviceMemory buffer_memory;
vk::BufferView buffer_view;
uint32_t size = 0;
};
}

View File

@@ -18,6 +18,9 @@ bool VKInstance::Create(vk::Instance instance, vk::PhysicalDevice physical_devic
this->instance = instance;
this->physical_device = physical_device;
// Get physical device limits
device_limits = physical_device.getProperties().limits;
// Determine required extensions and features
if (!FindExtensions() || !FindFeatures())
return false;

View File

@@ -34,6 +34,7 @@ public:
/// Feature support
bool SupportsAnisotropicFiltering() const;
u32 UniformMinAlignment() const { return device_limits.minUniformBufferOffsetAlignment; }
private:
bool CreateDevice(vk::SurfaceKHR surface, bool validation_enabled);
@@ -54,6 +55,7 @@ public:
std::vector<const char*> device_extensions;
vk::PhysicalDeviceFeatures device_features{};
vk::PhysicalDeviceVulkan12Features new_features{};
vk::PhysicalDeviceLimits device_limits;
};
extern std::unique_ptr<VKInstance> g_vk_instace;

File diff suppressed because it is too large Load Diff

View File

@@ -23,6 +23,8 @@
#include "video_core/regs_rasterizer.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;
@@ -31,10 +33,89 @@ class EmuWindow;
namespace Vulkan {
class ShaderProgramManager;
enum class UniformBindings : u32 { Common, VS, GS };
struct LightSrc {
alignas(16) glm::vec3 specular_0;
alignas(16) glm::vec3 specular_1;
alignas(16) glm::vec3 diffuse;
alignas(16) glm::vec3 ambient;
alignas(16) glm::vec3 position;
alignas(16) glm::vec3 spot_direction; // negated
float dist_atten_bias;
float dist_atten_scale;
};
/// Uniform structure for the Uniform Buffer Object, all vectors must be 16-byte aligned
// NOTE: Always keep a vec4 at the end. The GL spec is not clear wether the alignment at
// the end of a uniform block is included in UNIFORM_BLOCK_DATA_SIZE or not.
// Not following that rule will cause problems on some AMD drivers.
struct UniformData {
int framebuffer_scale;
int alphatest_ref;
float depth_scale;
float depth_offset;
float shadow_bias_constant;
float shadow_bias_linear;
int scissor_x1;
int scissor_y1;
int scissor_x2;
int scissor_y2;
int fog_lut_offset;
int proctex_noise_lut_offset;
int proctex_color_map_offset;
int proctex_alpha_map_offset;
int proctex_lut_offset;
int proctex_diff_lut_offset;
float proctex_bias;
int shadow_texture_bias;
alignas(16) glm::ivec4 lighting_lut_offset[Pica::LightingRegs::NumLightingSampler / 4];
alignas(16) glm::vec3 fog_color;
alignas(8) glm::vec2 proctex_noise_f;
alignas(8) glm::vec2 proctex_noise_a;
alignas(8) glm::vec2 proctex_noise_p;
alignas(16) glm::vec3 lighting_global_ambient;
LightSrc light_src[8];
alignas(16) glm::vec4 const_color[6]; // A vec4 color for each of the six tev stages
alignas(16) glm::vec4 tev_combiner_buffer_color;
alignas(16) glm::vec4 clip_coef;
};
static_assert(
sizeof(UniformData) == 0x4F0,
"The size of the UniformData structure has changed, update the structure in the shader");
static_assert(sizeof(UniformData) < 16384,
"UniformData structure must be less than 16kb as per the OpenGL spec");
/// Uniform struct for the Uniform Buffer Object that contains PICA vertex/geometry shader uniforms.
// NOTE: the same rule from UniformData also applies here.
struct PicaUniformsData {
void SetFromRegs(const Pica::ShaderRegs& regs, const Pica::Shader::ShaderSetup& setup);
struct BoolAligned {
alignas(16) int b;
};
std::array<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 RasterizerVulkan : public VideoCore::RasterizerInterface {
public:
explicit RasterizerVulkan(Frontend::EmuWindow& emu_window);
~RasterizerVulkan() override;
~RasterizerVulkan() override = default;
void LoadDiskResources(const std::atomic_bool& stop_loading,
const VideoCore::DiskResourceLoadCallback& callback) override;
@@ -52,43 +133,17 @@ public:
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, OpenGL::ScreenInfo& screen_info) override;
u32 pixel_stride, Vulkan::ScreenInfo& screen_info) override;
bool AccelerateDrawBatch(bool is_indexed) override;
/// Syncs entire status to match PICA registers
void SyncEntireState() override;
private:
struct SamplerInfo {
using TextureConfig = Pica::TexturingRegs::TextureConfig;
OGLSampler sampler;
/// Creates the sampler object, initializing its state so that it's in sync with the
/// SamplerInfo struct.
void Create();
/// Syncs the sampler object with the config, updating any necessary state.
void SyncWithConfig(const TextureConfig& config);
private:
TextureConfig::TextureFilter mag_filter;
TextureConfig::TextureFilter min_filter;
TextureConfig::TextureFilter mip_filter;
TextureConfig::WrapMode wrap_s;
TextureConfig::WrapMode wrap_t;
u32 border_color;
u32 lod_min;
u32 lod_max;
s32 lod_bias;
// TODO(wwylele): remove this once mipmap for cube is implemented
bool supress_mipmap_for_cube = false;
};
struct VertexInfo
struct VertexBase
{
VertexInfo() = default;
VertexInfo(const Pica::Shader::OutputVertex& v, bool flip_quaternion) {
VertexBase() = default;
VertexBase(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();
@@ -128,21 +183,21 @@ private:
};
/// Structure that the hardware rendered vertices are composed of
struct HardwareVertex : public VertexInfo
struct HardwareVertex : public VertexBase
{
HardwareVertex() = default;
HardwareVertex(const Pica::Shader::OutputVertex& v, bool flip_quaternion) : VertexInfo(v, flip_quaternion) {};
static constexpr auto binding_desc = vk::VertexInputBindingDescription(0, sizeof(VertexInfo));
HardwareVertex(const Pica::Shader::OutputVertex& v, bool flip_quaternion) : VertexBase(v, flip_quaternion) {};
static constexpr auto binding_desc = vk::VertexInputBindingDescription(0, sizeof(VertexBase));
static constexpr std::array<vk::VertexInputAttributeDescription, 8> attribute_desc =
{
vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32B32A32Sfloat, offsetof(VertexInfo, position)),
vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32B32A32Sfloat, offsetof(VertexInfo, color)),
vk::VertexInputAttributeDescription(2, 0, vk::Format::eR32G32Sfloat, offsetof(VertexInfo, tex_coord0)),
vk::VertexInputAttributeDescription(3, 0, vk::Format::eR32G32Sfloat, offsetof(VertexInfo, tex_coord1)),
vk::VertexInputAttributeDescription(4, 0, vk::Format::eR32G32Sfloat, offsetof(VertexInfo, tex_coord2)),
vk::VertexInputAttributeDescription(5, 0, vk::Format::eR32Sfloat, offsetof(VertexInfo, tex_coord0_w)),
vk::VertexInputAttributeDescription(6, 0, vk::Format::eR32G32B32A32Sfloat, offsetof(VertexInfo, normquat)),
vk::VertexInputAttributeDescription(7, 0, vk::Format::eR32G32B32Sfloat, offsetof(VertexInfo, view)),
vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32B32A32Sfloat, offsetof(VertexBase, position)),
vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32B32A32Sfloat, offsetof(VertexBase, color)),
vk::VertexInputAttributeDescription(2, 0, vk::Format::eR32G32Sfloat, offsetof(VertexBase, tex_coord0)),
vk::VertexInputAttributeDescription(3, 0, vk::Format::eR32G32Sfloat, offsetof(VertexBase, tex_coord1)),
vk::VertexInputAttributeDescription(4, 0, vk::Format::eR32G32Sfloat, offsetof(VertexBase, tex_coord2)),
vk::VertexInputAttributeDescription(5, 0, vk::Format::eR32Sfloat, offsetof(VertexBase, tex_coord0_w)),
vk::VertexInputAttributeDescription(6, 0, vk::Format::eR32G32B32A32Sfloat, offsetof(VertexBase, normquat)),
vk::VertexInputAttributeDescription(7, 0, vk::Format::eR32G32B32Sfloat, offsetof(VertexBase, view)),
};
};
@@ -254,30 +309,16 @@ private:
/// Generic draw function for DrawTriangles and AccelerateDrawBatch
bool Draw(bool accelerate, bool is_indexed);
/// Internal implementation for AccelerateDrawBatch
bool AccelerateDrawBatchInternal(bool is_indexed);
struct VertexArrayInfo {
u32 vs_input_index_min;
u32 vs_input_index_max;
u32 vs_input_size;
};
/// Retrieve the range and the size of the input vertex
VertexArrayInfo AnalyzeVertexArray(bool is_indexed);
private:
VulkanState state;
/// Setup vertex shader for AccelerateDrawBatch
bool SetupVertexShader();
/// Setup geometry shader for AccelerateDrawBatch
bool SetupGeometryShader();
bool is_amd;
OpenGLState state;
GLuint default_texture;
RasterizerCacheOpenGL res_cache;
RasterizerCacheVulkan res_cache;
std::vector<HardwareVertex> vertex_batch;
@@ -304,33 +345,23 @@ private:
static constexpr std::size_t UNIFORM_BUFFER_SIZE = 2 * 1024 * 1024;
static constexpr std::size_t TEXTURE_BUFFER_SIZE = 1 * 1024 * 1024;
OGLVertexArray hw_vao; // VAO for hardware shader / accelerate draw
std::array<bool, 16> hw_vao_enabled_attributes{};
std::array<SamplerInfo, 3> texture_samplers;
OGLStreamBuffer vertex_buffer;
OGLStreamBuffer uniform_buffer;
OGLStreamBuffer index_buffer;
OGLStreamBuffer texture_buffer;
OGLStreamBuffer texture_lf_buffer;
OGLFramebuffer framebuffer;
GLint uniform_buffer_alignment;
std::size_t uniform_size_aligned_vs;
std::size_t uniform_size_aligned_fs;
VKBuffer vertex_buffer, uniform_buffer, index_buffer;
VKBuffer texture_buffer_lut_lf, texture_buffer_lut;
u32 uniform_buffer_alignment;
u32 uniform_size_aligned_vs, uniform_size_aligned_fs;
SamplerInfo texture_cube_sampler;
OGLTexture texture_buffer_lut_lf;
OGLTexture texture_buffer_lut_rg;
OGLTexture texture_buffer_lut_rgba;
std::array<std::array<GLvec2, 256>, Pica::LightingRegs::NumLightingSampler> lighting_lut_data{};
std::array<GLvec2, 128> fog_lut_data{};
std::array<GLvec2, 128> proctex_noise_lut_data{};
std::array<GLvec2, 128> proctex_color_map_data{};
std::array<GLvec2, 128> proctex_alpha_map_data{};
std::array<GLvec4, 256> proctex_lut_data{};
std::array<GLvec4, 256> proctex_diff_lut_data{};
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;
};

View File

@@ -38,6 +38,14 @@ namespace Vulkan {
using SurfaceType = SurfaceParams::SurfaceType;
using PixelFormat = SurfaceParams::PixelFormat;
static constexpr std::array<vk::Format, 5> fb_format_tuples = {{
vk::Format::eR8G8B8A8Uint, // RGBA8
vk::Format::eR8G8B8Uint, // RGB8
vk::Format::eR5G5B5A1UnormPack16, // RGB5A1
vk::Format::eR5G6B5UnormPack16, // RGB565
vk::Format::eR4G4B4A4UnormPack16, // RGBA4
}};
template <typename Map, typename Interval>
static constexpr auto RangeFromInterval(Map& map, const Interval& interval) {
return boost::make_iterator_range(map.equal_range(interval));
@@ -55,13 +63,33 @@ static void MortonCopyTile(u32 stride, u8* tile_buffer, u8* gpu_buffer) {
if constexpr (format == PixelFormat::D24S8) {
gpu_ptr[0] = tile_ptr[3];
std::memcpy(gpu_ptr + 1, tile_ptr, 3);
} else {
std::memcpy(gpu_ptr, tile_ptr, bytes_per_pixel);
}
} else if (format == PixelFormat::RGBA8) {
gpu_ptr[0] = tile_ptr[3];
gpu_ptr[1] = tile_ptr[2];
gpu_ptr[2] = tile_ptr[1];
gpu_ptr[3] = tile_ptr[0];
} else if (format == PixelFormat::RGB8) {
gpu_ptr[0] = tile_ptr[2];
gpu_ptr[1] = tile_ptr[1];
gpu_ptr[2] = tile_ptr[0];
} else {
std::memcpy(gpu_ptr, tile_ptr, bytes_per_pixel);
}
} else {
if constexpr (format == PixelFormat::D24S8) {
std::memcpy(tile_ptr, gpu_ptr + 1, 3);
tile_ptr[3] = gpu_ptr[0];
} else if (format == PixelFormat::RGBA8) {
// because GLES does not have ABGR format
// so we will do byteswapping here
tile_ptr[0] = gpu_ptr[3];
tile_ptr[1] = gpu_ptr[2];
tile_ptr[2] = gpu_ptr[1];
tile_ptr[3] = gpu_ptr[0];
} else if (format == PixelFormat::RGB8) {
tile_ptr[0] = gpu_ptr[2];
tile_ptr[1] = gpu_ptr[1];
tile_ptr[2] = gpu_ptr[0];
} else {
std::memcpy(tile_ptr, gpu_ptr, bytes_per_pixel);
}
@@ -205,76 +233,6 @@ VKTexture RasterizerCacheVulkan::AllocateSurfaceTexture(vk::Format format, u32 w
return texture;
}
static bool FillSurface(const Surface& surface, const u8* fill_data,
const Common::Rectangle<u32>& fill_rect) {
OpenGLState prev_state = OpenGLState::GetCurState();
SCOPE_EXIT({ prev_state.Apply(); });
OpenGLState state;
state.scissor.enabled = true;
state.scissor.x = static_cast<GLint>(fill_rect.left);
state.scissor.y = static_cast<GLint>(fill_rect.bottom);
state.scissor.width = static_cast<GLsizei>(fill_rect.GetWidth());
state.scissor.height = static_cast<GLsizei>(fill_rect.GetHeight());
state.draw.draw_framebuffer = draw_fb_handle;
state.Apply();
surface->InvalidateAllWatcher();
if (surface->type == SurfaceType::Color || surface->type == SurfaceType::Texture) {
Pica::Texture::TextureInfo tex_info{};
tex_info.format = static_cast<Pica::TexturingRegs::TextureFormat>(surface->pixel_format);
Common::Vec4<u8> color = Pica::Texture::LookupTexture(fill_data, 0, 0, tex_info);
std::array<float, 4> color_values = {color.x / 255.f, color.y / 255.f, color.z / 255.f,
color.w / 255.f};
state.color_mask.red_enabled = GL_TRUE;
state.color_mask.green_enabled = GL_TRUE;
state.color_mask.blue_enabled = GL_TRUE;
state.color_mask.alpha_enabled = GL_TRUE;
state.Apply();
glClearBufferfv(GL_COLOR, 0, &color_values[0]);
} else if (surface->type == SurfaceType::Depth) {
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D,
surface->texture.handle, 0);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
u32 value_32bit = 0;
GLfloat value_float;
if (surface->pixel_format == SurfaceParams::PixelFormat::D16) {
std::memcpy(&value_32bit, fill_data, 2);
value_float = value_32bit / 65535.0f; // 2^16 - 1
} else if (surface->pixel_format == SurfaceParams::PixelFormat::D24) {
std::memcpy(&value_32bit, fill_data, 3);
value_float = value_32bit / 16777215.0f; // 2^24 - 1
}
state.depth.write_mask = GL_TRUE;
state.Apply();
glClearBufferfv(GL_DEPTH, 0, &value_float);
} else if (surface->type == SurfaceType::DepthStencil) {
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D,
surface->texture.handle, 0);
u32 value_32bit;
std::memcpy(&value_32bit, fill_data, sizeof(u32));
GLfloat value_float = (value_32bit & 0xFFFFFF) / 16777215.0f; // 2^24 - 1
GLint value_int = (value_32bit >> 24);
state.depth.write_mask = GL_TRUE;
state.stencil.write_mask = -1;
state.Apply();
glClearBufferfi(GL_DEPTH_STENCIL, 0, value_float, value_int);
}
return true;
}
CachedSurface::~CachedSurface() {
if (texture.IsValid()) {
auto tag = is_custom ? HostTextureTag{GetFormatTuple(PixelFormat::RGBA8),
@@ -339,23 +297,13 @@ void RasterizerCacheVulkan::CopySurface(const Surface& src_surface, const Surfac
// This is only called when CanCopy is true, no need to run checks here
if (src_surface->type == SurfaceType::Fill) {
// FillSurface needs a 4 bytes buffer
const u32 fill_offset =
(boost::icl::first(copy_interval) - src_surface->addr) % src_surface->fill_size;
std::array<u8, 4> fill_buffer;
u32 fill_buff_pos = fill_offset;
for (int i : {0, 1, 2, 3})
fill_buffer[i] = src_surface->fill_data[fill_buff_pos++ % src_surface->fill_size];
FillSurface(dst_surface, &fill_buffer[0], dst_surface->GetScaledSubRect(subrect_params),
draw_framebuffer.handle);
return;
// NO-OP Vulkan does not allow easy clearing for arbitary textures with rectangle
printf("bad!");
}
if (src_surface->CanSubRect(subrect_params)) {
auto srect = src_surface->GetScaledSubRect(subrect_params);
auto drect = dst_surface->GetScaledSubRect(subrect_params);
src_surface->texture.BlitTo(srect, dst_surface->texture, drect, src_surface->type);
src_surface->texture.BlitTo(srect, &dst_surface->texture, drect, src_surface->type);
return;
}
@@ -363,7 +311,7 @@ void RasterizerCacheVulkan::CopySurface(const Surface& src_surface, const Surfac
}
MICROPROFILE_DEFINE(Vulkan_SurfaceLoad, "Vulkan", "Surface Load", MP_RGB(128, 192, 64));
void CachedSurface::LoadGLBuffer(PAddr load_start, PAddr load_end) {
void CachedSurface::LoadGPUBuffer(PAddr load_start, PAddr load_end) {
ASSERT(type != SurfaceType::Fill);
const u8* const texture_src_data = VideoCore::g_memory->GetPhysicalPointer(addr);
@@ -371,7 +319,7 @@ void CachedSurface::LoadGLBuffer(PAddr load_start, PAddr load_end) {
return;
if (vk_buffer.empty()) {
vk_buffer.resize(width * height * GetGLBytesPerPixel(pixel_format));
vk_buffer.resize(width * height * GetBytesPerPixel(pixel_format));
}
// TODO: Should probably be done in ::Memory:: and check for other regions too
@@ -419,12 +367,12 @@ void CachedSurface::LoadGLBuffer(PAddr load_start, PAddr load_end) {
}
MICROPROFILE_DEFINE(OpenGL_SurfaceFlush, "OpenGL", "Surface Flush", MP_RGB(128, 192, 64));
void CachedSurface::FlushGLBuffer(PAddr flush_start, PAddr flush_end) {
void CachedSurface::FlushGPUBuffer(PAddr flush_start, PAddr flush_end) {
u8* const dst_buffer = VideoCore::g_memory->GetPhysicalPointer(addr);
if (dst_buffer == nullptr)
return;
ASSERT(gl_buffer.size() == width * height * GetGLBytesPerPixel(pixel_format));
ASSERT(vk_buffer.size() == width * height * GetBytesPerPixel(pixel_format));
// TODO: Should probably be done in ::Memory:: and check for other regions too
// same as loadglbuffer()
@@ -455,135 +403,42 @@ void CachedSurface::FlushGLBuffer(PAddr flush_start, PAddr flush_end) {
if (backup_bytes)
std::memcpy(&dst_buffer[coarse_start_offset], &backup_data[0], backup_bytes);
} else if (!is_tiled) {
ASSERT(type == SurfaceType::Color);
if (pixel_format == PixelFormat::RGBA8 && GLES) {
if (pixel_format == PixelFormat::RGBA8) {
for (std::size_t i = start_offset; i < flush_end - addr; i += 4) {
dst_buffer[i] = gl_buffer[i + 3];
dst_buffer[i + 1] = gl_buffer[i + 2];
dst_buffer[i + 2] = gl_buffer[i + 1];
dst_buffer[i + 3] = gl_buffer[i];
dst_buffer[i] = vk_buffer[i + 3];
dst_buffer[i + 1] = vk_buffer[i + 2];
dst_buffer[i + 2] = vk_buffer[i + 1];
dst_buffer[i + 3] = vk_buffer[i];
}
} else if (pixel_format == PixelFormat::RGB8 && GLES) {
} else if (pixel_format == PixelFormat::RGB8) {
for (std::size_t i = start_offset; i < flush_end - addr; i += 3) {
dst_buffer[i] = gl_buffer[i + 2];
dst_buffer[i + 1] = gl_buffer[i + 1];
dst_buffer[i + 2] = gl_buffer[i];
dst_buffer[i] = vk_buffer[i + 2];
dst_buffer[i + 1] = vk_buffer[i + 1];
dst_buffer[i + 2] = vk_buffer[i];
}
} else {
std::memcpy(dst_buffer + start_offset, &gl_buffer[start_offset],
std::memcpy(dst_buffer + start_offset, &vk_buffer[start_offset],
flush_end - flush_start);
}
} else {
gpu_to_morton_fns[static_cast<std::size_t>(pixel_format)](stride, height, &gl_buffer[0],
gpu_to_morton_fns[static_cast<std::size_t>(pixel_format)](stride, height, &vk_buffer[0],
addr, flush_start, flush_end);
}
}
bool CachedSurface::LoadCustomTexture(u64 tex_hash) {
auto& custom_tex_cache = Core::System::GetInstance().CustomTexCache();
const auto& image_interface = Core::System::GetInstance().GetImageInterface();
if (custom_tex_cache.IsTextureCached(tex_hash)) {
custom_tex_info = custom_tex_cache.LookupTexture(tex_hash);
return true;
}
if (!custom_tex_cache.CustomTextureExists(tex_hash)) {
return false;
}
const auto& path_info = custom_tex_cache.LookupTexturePathInfo(tex_hash);
if (!image_interface->DecodePNG(custom_tex_info.tex, custom_tex_info.width,
custom_tex_info.height, path_info.path)) {
LOG_ERROR(Render_OpenGL, "Failed to load custom texture {}", path_info.path);
return false;
}
const std::bitset<32> width_bits(custom_tex_info.width);
const std::bitset<32> height_bits(custom_tex_info.height);
if (width_bits.count() != 1 || height_bits.count() != 1) {
LOG_ERROR(Render_OpenGL, "Texture {} size is not a power of 2", path_info.path);
return false;
}
LOG_DEBUG(Render_OpenGL, "Loaded custom texture from {}", path_info.path);
Common::FlipRGBA8Texture(custom_tex_info.tex, custom_tex_info.width, custom_tex_info.height);
custom_tex_cache.CacheTexture(tex_hash, custom_tex_info.tex, custom_tex_info.width,
custom_tex_info.height);
return true;
}
void CachedSurface::DumpTexture(GLuint target_tex, u64 tex_hash) {
// Make sure the texture size is a power of 2
// If not, the surface is actually a framebuffer
std::bitset<32> width_bits(width);
std::bitset<32> height_bits(height);
if (width_bits.count() != 1 || height_bits.count() != 1) {
LOG_WARNING(Render_OpenGL, "Not dumping {:016X} because size isn't a power of 2 ({}x{})",
tex_hash, width, height);
return;
}
// Dump texture to RGBA8 and encode as PNG
const auto& image_interface = Core::System::GetInstance().GetImageInterface();
auto& custom_tex_cache = Core::System::GetInstance().CustomTexCache();
std::string dump_path =
fmt::format("{}textures/{:016X}/", FileUtil::GetUserPath(FileUtil::UserPath::DumpDir),
Core::System::GetInstance().Kernel().GetCurrentProcess()->codeset->program_id);
if (!FileUtil::CreateFullPath(dump_path)) {
LOG_ERROR(Render, "Unable to create {}", dump_path);
return;
}
dump_path += fmt::format("tex1_{}x{}_{:016X}_{}.png", width, height, tex_hash, pixel_format);
if (!custom_tex_cache.IsTextureDumped(tex_hash) && !FileUtil::Exists(dump_path)) {
custom_tex_cache.SetTextureDumped(tex_hash);
LOG_INFO(Render_OpenGL, "Dumping texture to {}", dump_path);
std::vector<u8> decoded_texture;
decoded_texture.resize(width * height * 4);
OpenGLState state = OpenGLState::GetCurState();
GLuint old_texture = state.texture_units[0].texture_2d;
state.Apply();
/*
GetTexImageOES is used even if not using OpenGL ES to work around a small issue that
happens if using custom textures with texture dumping at the same.
Let's say there's 2 textures that are both 32x32 and one of them gets replaced with a
higher quality 256x256 texture. If the 256x256 texture is displayed first and the
32x32 texture gets uploaded to the same underlying OpenGL texture, the 32x32 texture
will appear in the corner of the 256x256 texture. If texture dumping is enabled and
the 32x32 is undumped, Citra will attempt to dump it. Since the underlying OpenGL
texture is still 256x256, Citra crashes because it thinks the texture is only 32x32.
GetTexImageOES conveniently only dumps the specified region, and works on both
desktop and ES.
*/
// if the backend isn't OpenGL ES, this won't be initialized yet
if (!owner.texture_downloader_es)
owner.texture_downloader_es = std::make_unique<TextureDownloaderES>(false);
owner.texture_downloader_es->GetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE,
height, width, &decoded_texture[0]);
state.texture_units[0].texture_2d = old_texture;
state.Apply();
Common::FlipRGBA8Texture(decoded_texture, width, height);
if (!image_interface->EncodePNG(dump_path, decoded_texture, width, height))
LOG_ERROR(Render_OpenGL, "Failed to save decoded texture");
}
}
MICROPROFILE_DEFINE(OpenGL_TextureUL, "OpenGL", "Texture Upload", MP_RGB(128, 192, 64));
void CachedSurface::UploadGLTexture(Common::Rectangle<u32> rect, GLuint read_fb_handle,
GLuint draw_fb_handle) {
void CachedSurface::UploadGPUTexture(Common::Rectangle<u32> rect) {
if (type == SurfaceType::Fill)
return;
MICROPROFILE_SCOPE(OpenGL_TextureUL);
ASSERT(gl_buffer.size() == width * height * GetGLBytesPerPixel(pixel_format));
ASSERT(vk_buffer.size() == width * height * GetBytesPerPixel(pixel_format));
u64 tex_hash = 0;
if (Settings::values.dump_textures || Settings::values.custom_textures) {
tex_hash = Common::ComputeHash64(gl_buffer.data(), gl_buffer.size());
tex_hash = Common::ComputeHash64(vk_buffer.data(), vk_buffer.size());
}
if (Settings::values.custom_textures) {
@@ -591,62 +446,49 @@ void CachedSurface::UploadGLTexture(Common::Rectangle<u32> rect, GLuint read_fb_
}
// Load data from memory to the surface
GLint x0 = static_cast<GLint>(rect.left);
GLint y0 = static_cast<GLint>(rect.bottom);
std::size_t buffer_offset = (y0 * stride + x0) * GetGLBytesPerPixel(pixel_format);
int x0 = static_cast<int>(rect.left);
int y0 = static_cast<int>(rect.bottom);
std::size_t buffer_offset = (y0 * stride + x0) * GetBytesPerPixel(pixel_format);
const FormatTuple& tuple = GetFormatTuple(pixel_format);
GLuint target_tex = texture.handle;
//const FormatTuple& tuple = GetFormatTuple(pixel_format);
//GLuint target_tex = texture.handle;
// If not 1x scale, create 1x texture that we will blit from to replace texture subrect in
// surface
OGLTexture unscaled_tex;
VKTexture unscaled_tex;
if (res_scale != 1) {
x0 = 0;
y0 = 0;
if (is_custom) {
unscaled_tex = owner.AllocateSurfaceTexture(
GetFormatTuple(PixelFormat::RGBA8), custom_tex_info.width, custom_tex_info.height);
} else {
unscaled_tex = owner.AllocateSurfaceTexture(tuple, rect.GetWidth(), rect.GetHeight());
}
target_tex = unscaled_tex.handle;
VKTexture::Info info = {
.width = rect.GetWidth(),
.height = rect.GetHeight(),
.format = fb_format_tuples[static_cast<u32>(pixel_format)],
.type = vk::ImageType::e2D,
.view_type = vk::ImageViewType::e2D
};
unscaled_tex.Create(info);
}
OpenGLState cur_state = OpenGLState::GetCurState();
//OpenGLState cur_state = OpenGLState::GetCurState();
GLuint old_tex = cur_state.texture_units[0].texture_2d;
cur_state.texture_units[0].texture_2d = target_tex;
cur_state.Apply();
//GLuint old_tex = cur_state.texture_units[0].texture_2d;
//cur_state.texture_units[0].texture_2d = target_tex;
//cur_state.Apply();
// Ensure no bad interactions with GL_UNPACK_ALIGNMENT
ASSERT(stride * GetGLBytesPerPixel(pixel_format) % 4 == 0);
if (is_custom) {
if (res_scale == 1) {
texture = owner.AllocateSurfaceTexture(GetFormatTuple(PixelFormat::RGBA8),
custom_tex_info.width, custom_tex_info.height);
cur_state.texture_units[0].texture_2d = texture.handle;
cur_state.Apply();
}
// always going to be using rgba8
glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(custom_tex_info.width));
//ASSERT(stride * GetBytesPerPixel(pixel_format) % 4 == 0);
//glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(stride));
glActiveTexture(GL_TEXTURE0);
glTexSubImage2D(GL_TEXTURE_2D, 0, x0, y0, custom_tex_info.width, custom_tex_info.height,
GL_RGBA, GL_UNSIGNED_BYTE, custom_tex_info.tex.data());
} else {
glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(stride));
glActiveTexture(GL_TEXTURE0);
//glActiveTexture(GL_TEXTURE0);
glTexSubImage2D(GL_TEXTURE_2D, 0, x0, y0, static_cast<GLsizei>(rect.GetWidth()),
static_cast<GLsizei>(rect.GetHeight()), tuple.format, tuple.type,
&gl_buffer[buffer_offset]);
}
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
if (Settings::values.dump_textures && !is_custom)
DumpTexture(target_tex, tex_hash);
//if (Settings::values.dump_textures && !is_custom)
// DumpTexture(target_tex, tex_hash);
cur_state.texture_units[0].texture_2d = old_tex;
cur_state.Apply();
@@ -671,8 +513,7 @@ void CachedSurface::UploadGLTexture(Common::Rectangle<u32> rect, GLuint read_fb_
}
MICROPROFILE_DEFINE(OpenGL_TextureDL, "OpenGL", "Texture Download", MP_RGB(128, 192, 64));
void CachedSurface::DownloadGLTexture(const Common::Rectangle<u32>& rect, GLuint read_fb_handle,
GLuint draw_fb_handle) {
void CachedSurface::DownloadGPUTexture(const Common::Rectangle<u32>& rect) {
if (type == SurfaceType::Fill) {
return;
}
@@ -870,8 +711,6 @@ RasterizerCacheVulkan::RasterizerCacheVulkan() {
texture_filterer = std::make_unique<TextureFilterer>(Settings::values.texture_filter_name,
resolution_scale_factor);
format_reinterpreter = std::make_unique<FormatReinterpreterOpenGL>();
if (GLES)
texture_downloader_es = std::make_unique<TextureDownloaderES>(false);
read_framebuffer.Create();
draw_framebuffer.Create();

View File

@@ -200,10 +200,6 @@ struct CachedSurface : SurfaceParams, std::enable_shared_from_this<CachedSurface
void LoadGPUBuffer(PAddr load_start, PAddr load_end);
void FlushGPUBuffer(PAddr flush_start, PAddr flush_end);
// Custom texture loading and dumping
bool LoadCustomTexture(u64 tex_hash);
void DumpTexture(VKTexture& target_tex, u64 tex_hash);
// 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);
@@ -342,9 +338,6 @@ private:
SurfaceMap dirty_regions;
SurfaceSet remove_surfaces;
VKFramebuffer read_framebuffer;
VKFramebuffer draw_framebuffer;
u16 resolution_scale_factor;
std::unordered_map<TextureCubeConfig, CachedTextureCube> texture_cube_cache;

View File

@@ -122,4 +122,379 @@ vk::RenderPass VKResourceCache::GetRenderPass(vk::Format color_format, vk::Forma
renderpass_cache.emplace(key, std::move(renderpass));
return handle;
}
Pipeline::Pipeline() { Clear(); }
void Pipeline::Clear()
{
m_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
m_ci.pNext = nullptr;
m_ci.flags = 0;
m_ci.pSetLayouts = nullptr;
m_ci.setLayoutCount = 0;
m_ci.pPushConstantRanges = nullptr;
m_ci.pushConstantRangeCount = 0;
}
void Pipeline::Build() {
VkPipelineLayout layout;
VkResult res = vkCreatePipelineLayout(device, &m_ci, nullptr, &layout);
if (res != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkCreatePipelineLayout() failed: ");
return VK_NULL_HANDLE;
}
Clear();
return layout;
}
void Pipeline::AddDescriptorSet(VkDescriptorSetLayout layout)
{
pxAssert(m_ci.setLayoutCount < MAX_SETS);
m_sets[m_ci.setLayoutCount] = layout;
m_ci.setLayoutCount++;
m_ci.pSetLayouts = m_sets.data();
}
void Pipeline::AddPushConstants(VkShaderStageFlags stages, u32 offset, u32 size)
{
pxAssert(m_ci.pushConstantRangeCount < MAX_PUSH_CONSTANTS);
VkPushConstantRange& r = m_push_constants[m_ci.pushConstantRangeCount];
r.stageFlags = stages;
r.offset = offset;
r.size = size;
m_ci.pushConstantRangeCount++;
m_ci.pPushConstantRanges = m_push_constants.data();
}
GraphicsPipelineBuilder::GraphicsPipelineBuilder() { Clear(); }
void GraphicsPipelineBuilder::Clear()
{
m_ci = {};
m_ci.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
m_shader_stages = {};
m_vertex_input_state = {};
m_vertex_input_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
m_ci.pVertexInputState = &m_vertex_input_state;
m_vertex_attributes = {};
m_vertex_buffers = {};
m_input_assembly = {};
m_input_assembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
m_rasterization_state = {};
m_rasterization_state.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
m_rasterization_state.lineWidth = 1.0f;
m_depth_state = {};
m_depth_state.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
m_blend_state = {};
m_blend_state.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
m_blend_attachments = {};
m_viewport_state = {};
m_viewport_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
m_viewport = {};
m_scissor = {};
m_dynamic_state = {};
m_dynamic_state.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
m_dynamic_state_values = {};
m_multisample_state = {};
m_multisample_state.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
m_provoking_vertex = {};
m_provoking_vertex.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_PROVOKING_VERTEX_STATE_CREATE_INFO_EXT;
// set defaults
SetNoCullRasterizationState();
SetNoDepthTestState();
SetNoBlendingState();
SetPrimitiveTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST);
// have to be specified even if dynamic
SetViewport(0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f);
SetScissorRect(0, 0, 1, 1);
SetMultisamples(VK_SAMPLE_COUNT_1_BIT);
}
VkPipeline GraphicsPipelineBuilder::Create(VkDevice device, VkPipelineCache pipeline_cache, bool clear /* = true */)
{
VkPipeline pipeline;
VkResult res = vkCreateGraphicsPipelines(device, pipeline_cache, 1, &m_ci, nullptr, &pipeline);
if (res != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkCreateGraphicsPipelines() failed: ");
return VK_NULL_HANDLE;
}
if (clear)
Clear();
return pipeline;
}
void GraphicsPipelineBuilder::SetShaderStage(
VkShaderStageFlagBits stage, VkShaderModule module, const char* entry_point)
{
pxAssert(m_ci.stageCount < MAX_SHADER_STAGES);
u32 index = 0;
for (; index < m_ci.stageCount; index++)
{
if (m_shader_stages[index].stage == stage)
break;
}
if (index == m_ci.stageCount)
{
m_ci.stageCount++;
m_ci.pStages = m_shader_stages.data();
}
VkPipelineShaderStageCreateInfo& s = m_shader_stages[index];
s.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
s.stage = stage;
s.module = module;
s.pName = entry_point;
}
void GraphicsPipelineBuilder::AddVertexBuffer(
u32 binding, u32 stride, VkVertexInputRate input_rate /*= VK_VERTEX_INPUT_RATE_VERTEX*/)
{
pxAssert(m_vertex_input_state.vertexAttributeDescriptionCount < MAX_VERTEX_BUFFERS);
VkVertexInputBindingDescription& b = m_vertex_buffers[m_vertex_input_state.vertexBindingDescriptionCount];
b.binding = binding;
b.stride = stride;
b.inputRate = input_rate;
m_vertex_input_state.vertexBindingDescriptionCount++;
m_vertex_input_state.pVertexBindingDescriptions = m_vertex_buffers.data();
m_ci.pVertexInputState = &m_vertex_input_state;
}
void GraphicsPipelineBuilder::AddVertexAttribute(u32 location, u32 binding, VkFormat format, u32 offset)
{
pxAssert(m_vertex_input_state.vertexAttributeDescriptionCount < MAX_VERTEX_BUFFERS);
VkVertexInputAttributeDescription& a =
m_vertex_attributes[m_vertex_input_state.vertexAttributeDescriptionCount];
a.location = location;
a.binding = binding;
a.format = format;
a.offset = offset;
m_vertex_input_state.vertexAttributeDescriptionCount++;
m_vertex_input_state.pVertexAttributeDescriptions = m_vertex_attributes.data();
m_ci.pVertexInputState = &m_vertex_input_state;
}
void GraphicsPipelineBuilder::SetPrimitiveTopology(
VkPrimitiveTopology topology, bool enable_primitive_restart /*= false*/)
{
m_input_assembly.topology = topology;
m_input_assembly.primitiveRestartEnable = enable_primitive_restart;
m_ci.pInputAssemblyState = &m_input_assembly;
}
void GraphicsPipelineBuilder::SetRasterizationState(
VkPolygonMode polygon_mode, VkCullModeFlags cull_mode, VkFrontFace front_face)
{
m_rasterization_state.polygonMode = polygon_mode;
m_rasterization_state.cullMode = cull_mode;
m_rasterization_state.frontFace = front_face;
m_ci.pRasterizationState = &m_rasterization_state;
}
void GraphicsPipelineBuilder::SetLineWidth(float width) { m_rasterization_state.lineWidth = width; }
void GraphicsPipelineBuilder::SetMultisamples(u32 multisamples, bool per_sample_shading)
{
m_multisample_state.rasterizationSamples = static_cast<VkSampleCountFlagBits>(multisamples);
m_multisample_state.sampleShadingEnable = per_sample_shading;
m_multisample_state.minSampleShading = (multisamples > 1) ? 1.0f : 0.0f;
}
void GraphicsPipelineBuilder::SetNoCullRasterizationState()
{
SetRasterizationState(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE, VK_FRONT_FACE_CLOCKWISE);
}
void GraphicsPipelineBuilder::SetDepthState(bool depth_test, bool depth_write, VkCompareOp compare_op)
{
m_depth_state.depthTestEnable = depth_test;
m_depth_state.depthWriteEnable = depth_write;
m_depth_state.depthCompareOp = compare_op;
m_ci.pDepthStencilState = &m_depth_state;
}
void GraphicsPipelineBuilder::SetStencilState(
bool stencil_test, const VkStencilOpState& front, const VkStencilOpState& back)
{
m_depth_state.stencilTestEnable = stencil_test;
m_depth_state.front = front;
m_depth_state.back = back;
}
void GraphicsPipelineBuilder::SetNoStencilState()
{
m_depth_state.stencilTestEnable = VK_FALSE;
m_depth_state.front = {};
m_depth_state.back = {};
}
void GraphicsPipelineBuilder::SetNoDepthTestState() { SetDepthState(false, false, VK_COMPARE_OP_ALWAYS); }
void GraphicsPipelineBuilder::SetBlendConstants(float r, float g, float b, float a)
{
m_blend_state.blendConstants[0] = r;
m_blend_state.blendConstants[1] = g;
m_blend_state.blendConstants[2] = b;
m_blend_state.blendConstants[3] = a;
m_ci.pColorBlendState = &m_blend_state;
}
void GraphicsPipelineBuilder::AddBlendAttachment(bool blend_enable, VkBlendFactor src_factor,
VkBlendFactor dst_factor, VkBlendOp op, VkBlendFactor alpha_src_factor, VkBlendFactor alpha_dst_factor,
VkBlendOp alpha_op,
VkColorComponentFlags
write_mask /* = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT */)
{
pxAssert(m_blend_state.attachmentCount < MAX_ATTACHMENTS);
VkPipelineColorBlendAttachmentState& bs = m_blend_attachments[m_blend_state.attachmentCount];
bs.blendEnable = blend_enable;
bs.srcColorBlendFactor = src_factor;
bs.dstColorBlendFactor = dst_factor;
bs.colorBlendOp = op;
bs.srcAlphaBlendFactor = alpha_src_factor;
bs.dstAlphaBlendFactor = alpha_dst_factor;
bs.alphaBlendOp = alpha_op;
bs.colorWriteMask = write_mask;
m_blend_state.attachmentCount++;
m_blend_state.pAttachments = m_blend_attachments.data();
m_ci.pColorBlendState = &m_blend_state;
}
void GraphicsPipelineBuilder::SetBlendAttachment(u32 attachment, bool blend_enable, VkBlendFactor src_factor,
VkBlendFactor dst_factor, VkBlendOp op, VkBlendFactor alpha_src_factor, VkBlendFactor alpha_dst_factor,
VkBlendOp alpha_op,
VkColorComponentFlags
write_mask /*= VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT*/)
{
pxAssert(attachment < MAX_ATTACHMENTS);
VkPipelineColorBlendAttachmentState& bs = m_blend_attachments[attachment];
bs.blendEnable = blend_enable;
bs.srcColorBlendFactor = src_factor;
bs.dstColorBlendFactor = dst_factor;
bs.colorBlendOp = op;
bs.srcAlphaBlendFactor = alpha_src_factor;
bs.dstAlphaBlendFactor = alpha_dst_factor;
bs.alphaBlendOp = alpha_op;
bs.colorWriteMask = write_mask;
if (attachment >= m_blend_state.attachmentCount)
{
m_blend_state.attachmentCount = attachment + 1u;
m_blend_state.pAttachments = m_blend_attachments.data();
m_ci.pColorBlendState = &m_blend_state;
}
}
void GraphicsPipelineBuilder::AddBlendFlags(u32 flags)
{
m_blend_state.flags |= flags;
}
void GraphicsPipelineBuilder::ClearBlendAttachments()
{
m_blend_attachments = {};
m_blend_state.attachmentCount = 0;
}
void GraphicsPipelineBuilder::SetNoBlendingState()
{
ClearBlendAttachments();
SetBlendAttachment(0, false, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_ZERO, VK_BLEND_OP_ADD, VK_BLEND_FACTOR_ONE,
VK_BLEND_FACTOR_ZERO, VK_BLEND_OP_ADD,
VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT);
}
void GraphicsPipelineBuilder::AddDynamicState(VkDynamicState state)
{
pxAssert(m_dynamic_state.dynamicStateCount < MAX_DYNAMIC_STATE);
m_dynamic_state_values[m_dynamic_state.dynamicStateCount] = state;
m_dynamic_state.dynamicStateCount++;
m_dynamic_state.pDynamicStates = m_dynamic_state_values.data();
m_ci.pDynamicState = &m_dynamic_state;
}
void GraphicsPipelineBuilder::SetDynamicViewportAndScissorState()
{
AddDynamicState(VK_DYNAMIC_STATE_VIEWPORT);
AddDynamicState(VK_DYNAMIC_STATE_SCISSOR);
}
void GraphicsPipelineBuilder::SetViewport(
float x, float y, float width, float height, float min_depth, float max_depth)
{
m_viewport.x = x;
m_viewport.y = y;
m_viewport.width = width;
m_viewport.height = height;
m_viewport.minDepth = min_depth;
m_viewport.maxDepth = max_depth;
m_viewport_state.pViewports = &m_viewport;
m_viewport_state.viewportCount = 1u;
m_ci.pViewportState = &m_viewport_state;
}
void GraphicsPipelineBuilder::SetScissorRect(s32 x, s32 y, u32 width, u32 height)
{
m_scissor.offset.x = x;
m_scissor.offset.y = y;
m_scissor.extent.width = width;
m_scissor.extent.height = height;
m_viewport_state.pScissors = &m_scissor;
m_viewport_state.scissorCount = 1u;
m_ci.pViewportState = &m_viewport_state;
}
void GraphicsPipelineBuilder::SetMultisamples(VkSampleCountFlagBits samples)
{
m_multisample_state.rasterizationSamples = samples;
m_ci.pMultisampleState = &m_multisample_state;
}
void GraphicsPipelineBuilder::SetPipelineLayout(VkPipelineLayout layout) { m_ci.layout = layout; }
void GraphicsPipelineBuilder::SetRenderPass(VkRenderPass render_pass, u32 subpass)
{
m_ci.renderPass = render_pass;
m_ci.subpass = subpass;
}
void GraphicsPipelineBuilder::SetProvokingVertex(VkProvokingVertexModeEXT mode)
{
Util::AddPointerToChain(&m_rasterization_state, &m_provoking_vertex);
m_provoking_vertex.provokingVertexMode = mode;
}
} // namespace Vulkan

View File

@@ -53,6 +53,56 @@ private:
std::string pipeline_cache_filename;
};
constexpr u32 MAX_DYNAMIC_STATES = 8;
constexpr u32 MAX_ATTACHMENTS = 2;
constexpr u32 MAX_VERTEX_BUFFERS = 3;
class Pipeline {
public:
Pipeline();
~Pipeline() = default;
void Build();
void SetShaderStage(vk::ShaderStageFlagBits stage, vk::ShaderModule module);
void AddVertexBuffer(u32 binding, u32 stride, vk::VertexInputRate input_rate);
void AddVertexAttribute(u32 location, u32 binding, VkFormat format, u32 offset);
void SetPrimitiveTopology(vk::PrimitiveTopology topology, bool enable_primitive_restart = false);
void SetRasterizationState(vk::PolygonMode polygon_mode, vk::CullModeFlags cull_mode,
vk::FrontFace front_face);
void SetDepthState(bool depth_test, bool depth_write, vk::CompareOp compare_op);
void SetStencilState(bool stencil_test, vk::StencilOpState front, vk::StencilOpState back);
void SetNoDepthTestState();
void SetNoStencilState();
void AddDynamicState(vk::DynamicState state);
void SetMultisamples(VkSampleCountFlagBits samples);
private:
vk::GraphicsPipelineCreateInfo pipeline_info;
std::array<vk::PipelineShaderStageCreateInfo, 3> shader_stages;
vk::PipelineVertexInputStateCreateInfo vertex_input_state;
vk::PipelineInputAssemblyStateCreateInfo input_assembly;
vk::PipelineRasterizationStateCreateInfo rasterization_state;
vk::PipelineDepthStencilStateCreateInfo depth_state;
// Blending
vk::PipelineColorBlendStateCreateInfo blend_state;
std::array<vk::PipelineColorBlendAttachmentState, MAX_ATTACHMENTS> blend_attachments;
std::array<vk::DynamicState, MAX_DYNAMIC_STATES> dynamic_state_values;
VkPipelineViewportStateCreateInfo m_viewport_state;
VkViewport m_viewport;
VkRect2D m_scissor;
VkPipelineDynamicStateCreateInfo m_dynamic_state;
vk::PipelineMultisampleStateCreateInfo multisample_info;
};
extern std::unique_ptr<VKResourceCache> g_vk_res_cache;
} // namespace Vulkan

View File

@@ -2,25 +2,37 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <span>
#include <shaderc/shaderc.hpp>
#include "video_core/renderer_vulkan/vk_state.h"
#include "video_core/renderer_vulkan/vk_task_scheduler.h"
#include "video_core/renderer_vulkan/vk_resource_cache.h"
#include "video_core/renderer_opengl/gl_shader_gen.h"
#include "video_core/renderer_opengl/gl_shader_decompiler.h"
namespace Vulkan {
std::unique_ptr<VulkanState> g_vk_state;
// Define bitwise operators for DirtyState enum
DirtyState operator |=(DirtyState lhs, DirtyState rhs) {
return static_cast<DirtyState> (
// Define bitwise operators for DirtyFlags enum
DirtyFlags operator |=(DirtyFlags lhs, DirtyFlags rhs) {
return static_cast<DirtyFlags> (
static_cast<unsigned>(lhs) |
static_cast<unsigned>(rhs)
);
}
bool operator &(DirtyState lhs, DirtyState rhs) {
return static_cast<unsigned>(lhs) &
static_cast<unsigned>(rhs);
bool operator &(DirtyFlags lhs, DirtyFlags rhs) {
return static_cast<u32>(lhs) &
static_cast<u32>(rhs);
}
bool operator >(BindingID lhs, BindingID rhs) {
return static_cast<u32>(lhs) &
static_cast<u32>(rhs);
}
bool operator <(BindingID lhs, BindingID rhs) {
return static_cast<u32>(lhs) &
static_cast<u32>(rhs);
}
void VulkanState::Create() {
@@ -34,7 +46,7 @@ void VulkanState::Create() {
};
dummy_texture.Create(info);
dummy_texture.TransitionLayout(vk::ImageLayout::eShaderReadOnlyOptimal, g_vk_task_scheduler->GetCommandBuffer());
dummy_texture.Transition(vk::ImageLayout::eShaderReadOnlyOptimal);
// Create descriptor pool
// TODO: Choose sizes more wisely
@@ -53,7 +65,22 @@ void VulkanState::Create() {
vk::DescriptorSetAllocateInfo alloc_info(desc_pool.get(), layouts);
descriptor_sets = device.allocateDescriptorSetsUnique(alloc_info);
dirty_flags |= DirtyState::All;
// Create texture sampler
auto props = g_vk_instace->GetPhysicalDevice().getProperties();
vk::SamplerCreateInfo sampler_info
(
{}, vk::Filter::eNearest, vk::Filter::eNearest,
vk::SamplerMipmapMode::eNearest, vk::SamplerAddressMode::eClampToEdge,
vk::SamplerAddressMode::eClampToEdge, vk::SamplerAddressMode::eClampToEdge,
{}, true, props.limits.maxSamplerAnisotropy,
false, vk::CompareOp::eAlways, {}, {},
vk::BorderColor::eIntOpaqueBlack, false
);
sampler = g_vk_instace->GetDevice().createSamplerUnique(sampler_info);
dirty_flags |= DirtyFlags::All;
CompileTrivialShader();
}
void VulkanState::SetVertexBuffer(VKBuffer* buffer, vk::DeviceSize offset) {
@@ -63,56 +90,66 @@ void VulkanState::SetVertexBuffer(VKBuffer* buffer, vk::DeviceSize offset) {
vertex_buffer = buffer;
vertex_offset = offset;
dirty_flags |= DirtyState::VertexBuffer;
dirty_flags |= DirtyFlags::VertexBuffer;
}
void VulkanState::SetUniformBuffer(UniformID id, VKBuffer* buffer, u32 offset, u32 size) {
void VulkanState::SetUniformBuffer(BindingID id, VKBuffer* buffer, u32 offset, u32 size) {
assert(id < BindingID::Tex0);
u32 index = static_cast<u32>(id);
auto& binding = bindings.ubo[index];
if (binding.buffer != buffer->GetBuffer() || binding.range != size)
{
binding.buffer = buffer->GetBuffer();
binding.range = size;
dirty_flags |= DirtyState::Uniform;
bindings.ubo_update[index] = true;
auto& binding = bindings[index];
auto old_buffer = std::get<VKBuffer*>(binding.resource);
if (old_buffer != buffer) {
binding.resource = buffer;
dirty_flags |= DirtyFlags::Uniform;
binding.dirty = true;
}
}
void VulkanState::SetTexture(TextureID id, VKTexture* texture) {
void VulkanState::SetTexture(BindingID id, VKTexture* image) {
assert(id > BindingID::PicaUniform && id < BindingID::LutLF);
u32 index = static_cast<u32>(id);
if (bindings.texture[index].imageView == texture->GetView()) {
return;
}
bindings.texture[index].imageView = texture->GetView();
bindings.texture[index].imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
dirty_flags |= DirtyState::Texture;
bindings.texture_update[index] = true;
auto& binding = bindings[index];
auto old_image = std::get<VKTexture*>(binding.resource);
if (old_image != image) {
binding.resource = image;
dirty_flags |= DirtyFlags::Texture;
binding.dirty = true;
}
}
void VulkanState::SetTexelBuffer(TexelBufferID id, VKBuffer* buffer) {
void VulkanState::SetTexelBuffer(BindingID id, VKBuffer* buffer, vk::Format view_format) {
assert(id > BindingID::TexCube);
u32 index = static_cast<u32>(id);
if (bindings.lut[index].buffer == buffer->GetBuffer()) {
return;
}
bindings.lut[index].buffer = buffer->GetBuffer();
dirty_flags |= DirtyState::TexelBuffer;
bindings.lut_update[index] = true;
auto& binding = bindings[index];
auto old_buffer = std::get<VKBuffer*>(binding.resource);
if (old_buffer != buffer) {
auto& device = g_vk_instace->GetDevice();
binding.resource = buffer;
binding.buffer_view = device.createBufferViewUnique({{}, buffer->GetBuffer(), view_format});
dirty_flags |= DirtyFlags::TexelBuffer;
binding.dirty = true;
}
}
void VulkanState::UnbindTexture(VKTexture* image) {
// Search the texture bindings for the view
// and replace it with the dummy texture if found
for (auto& it : bindings.texture) {
if (it.imageView == image->GetView()) {
it.imageView = dummy_texture.GetView();
it.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
for (auto i = u32(BindingID::Tex0); i <= u32(BindingID::TexCube); i++) {
auto current_image = std::get<VKTexture*>(bindings[i].resource);
if (current_image == image) {
UnbindTexture(i);
}
}
}
void VulkanState::SetAttachments(VKTexture* color, VKTexture* depth_stencil) {
void VulkanState::UnbindTexture(u32 index) {
bindings[index].resource = &dummy_texture;
dirty_flags |= DirtyFlags::Texture;
}
void VulkanState::PushRenderTargets(VKTexture* color, VKTexture* depth_stencil) {
color_attachment = color;
depth_attachment = depth_stencil;
}
@@ -127,14 +164,8 @@ void VulkanState::BeginRendering() {
}
// Make sure attachments are in optimal layout
auto command_buffer = g_vk_task_scheduler->GetCommandBuffer();
if (color_attachment->GetLayout() != vk::ImageLayout::eColorAttachmentOptimal) {
color_attachment->TransitionLayout(vk::ImageLayout::eColorAttachmentOptimal, command_buffer);
}
if (depth_attachment->GetLayout() != vk::ImageLayout::eDepthStencilAttachmentOptimal) {
depth_attachment->TransitionLayout(vk::ImageLayout::eDepthStencilAttachmentOptimal, command_buffer);
}
color_attachment->Transition(vk::ImageLayout::eColorAttachmentOptimal);
depth_attachment->Transition(vk::ImageLayout::eDepthStencilAttachmentOptimal);
// Begin rendering
vk::RenderingAttachmentInfoKHR color_info(color_attachment->GetView(), color_attachment->GetLayout());
@@ -148,6 +179,7 @@ void VulkanState::BeginRendering() {
&depth_stencil_info
);
auto command_buffer = g_vk_task_scheduler->GetCommandBuffer();
command_buffer.beginRendering(render_info);
rendering = true;
}
@@ -163,21 +195,104 @@ void VulkanState::EndRendering() {
}
void VulkanState::SetViewport(vk::Viewport new_viewport) {
if (new_viewport == viewport) {
return;
if (new_viewport != viewport) {
viewport = new_viewport;
dirty_flags |= DirtyFlags::Viewport;
}
viewport = new_viewport;
dirty_flags |= DirtyState::Viewport;
}
void VulkanState::SetScissor(vk::Rect2D new_scissor) {
if (new_scissor == scissor) {
return;
if (new_scissor != scissor) {
scissor = new_scissor;
dirty_flags |= DirtyFlags::Scissor;
}
}
scissor = new_scissor;
dirty_flags |= DirtyState::Scissor;
void VulkanState::SetCullMode(vk::CullModeFlags flags) {
if (cull_mode != flags) {
cull_mode = flags;
dirty_flags |= DirtyFlags::CullMode;
}
}
void VulkanState::SetFrontFace(vk::FrontFace face) {
if (front_face != face) {
front_face = face;
dirty_flags |= DirtyFlags::FrontFace;
}
}
void VulkanState::SetLogicOp(vk::LogicOp new_logic_op) {
if (logic_op != new_logic_op) {
logic_op = new_logic_op;
dirty_flags |= DirtyFlags::LogicOp;
}
}
void VulkanState::SetColorMask(bool red, bool green, bool blue, bool alpha) {
auto mask = static_cast<vk::ColorComponentFlags>(red | (green << 1) | (blue << 2) | (alpha << 3));
static_state.blend.setColorWriteMask(mask);
}
void VulkanState::SetBlendEnable(bool enable) {
static_state.blend.setBlendEnable(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 = DirtyFlags::BlendConsts;
}
}
void VulkanState::SetStencilWrite(u32 mask) {
if (mask != stencil_write_mask) {
stencil_write_mask = mask;
dirty_flags |= DirtyFlags::StencilMask;
}
}
void VulkanState::SetStencilInput(u32 mask) {
if (mask != stencil_input_mask) {
stencil_input_mask = mask;
dirty_flags |= DirtyFlags::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;
compare_op = compare;
dirty_flags |= DirtyFlags::Stencil;
}
void VulkanState::SetDepthWrite(bool enable) {
if (enable != depth_writes) {
depth_writes = enable;
dirty_flags |= DirtyFlags::DepthWrite;
}
}
void VulkanState::SetDepthTest(bool enable, vk::CompareOp compare) {
depth_enabled = enable;
test_func = compare;
dirty_flags |= DirtyFlags::DepthTest;
}
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 = static_state.blend;
blend.setColorBlendOp(rgb_op);
blend.setAlphaBlendOp(alpha_op);
blend.setSrcColorBlendFactor(src_color);
blend.setDstColorBlendFactor(dst_color);
blend.setSrcAlphaBlendFactor(src_alpha);
blend.setDstAlphaBlendFactor(dst_alpha);
}
void VulkanState::Apply() {
@@ -189,56 +304,65 @@ void VulkanState::Apply() {
// Re-apply dynamic parts of the pipeline
auto command_buffer = g_vk_task_scheduler->GetCommandBuffer();
if (dirty_flags & DirtyState::VertexBuffer) {
if (dirty_flags & DirtyFlags::VertexBuffer) {
command_buffer.bindVertexBuffers(0, vertex_buffer->GetBuffer(), vertex_offset);
}
if (dirty_flags & DirtyState::IndexBuffer) {
if (dirty_flags & DirtyFlags::IndexBuffer) {
command_buffer.bindIndexBuffer(index_buffer->GetBuffer(), index_offset, vk::IndexType::eUint16);
}
if (dirty_flags & DirtyState::Viewport) {
if (dirty_flags & DirtyFlags::Viewport) {
command_buffer.setViewport(0, viewport);
}
if (dirty_flags & DirtyState::Scissor) {
if (dirty_flags & DirtyFlags::Scissor) {
command_buffer.setScissor(0, scissor);
}
dirty_flags = DirtyState::None;
dirty_flags = DirtyFlags::None;
}
void VulkanState::UpdateDescriptorSet() {
std::vector<vk::WriteDescriptorSet> writes;
std::vector<vk::DescriptorBufferInfo> buffer_infos;
std::vector<vk::DescriptorImageInfo> image_infos;
auto& device = g_vk_instace->GetDevice();
// Check if any resource has been updated
if (dirty_flags & DirtyState::Uniform) {
if (dirty_flags & DirtyFlags::Uniform) {
for (int i = 0; i < 2; i++) {
if (bindings.ubo_update[i]) {
if (bindings[i].dirty) {
auto buffer = std::get<VKBuffer*>(bindings[i].resource);
buffer_infos.emplace_back(buffer->GetBuffer(), 0, VK_WHOLE_SIZE);
writes.emplace_back(descriptor_sets[i].get(), i, 0, 1, vk::DescriptorType::eUniformBuffer,
nullptr, &bindings.ubo[i]);
bindings.ubo_update[i] = false;
nullptr, &buffer_infos.back(), nullptr);
bindings[i].dirty = false;
}
}
}
if (dirty_flags & DirtyState::Texture) {
for (int i = 0; i < 4; i++) {
if (bindings.texture_update[i]) {
if (dirty_flags & DirtyFlags::Texture) {
for (int i = 2; i < 6; i++) {
if (bindings[i].dirty) {
auto texture = std::get<VKTexture*>(bindings[i].resource);
image_infos.emplace_back(sampler.get(), texture->GetView(), vk::ImageLayout::eShaderReadOnlyOptimal);
writes.emplace_back(descriptor_sets[i].get(), i, 0, 1, vk::DescriptorType::eCombinedImageSampler,
nullptr, &bindings.texture[i]);
bindings.texture_update[i] = false;
&image_infos.back());
bindings[i].dirty = false;
}
}
}
if (dirty_flags & DirtyState::TexelBuffer) {
for (int i = 0; i < 3; i++) {
if (bindings.lut_update[i]) {
if (dirty_flags & DirtyFlags::TexelBuffer) {
for (int i = 6; i < 9; i++) {
if (bindings[i].dirty) {
auto buffer = std::get<VKBuffer*>(bindings[i].resource);
buffer_infos.emplace_back(buffer->GetBuffer(), 0, VK_WHOLE_SIZE);
writes.emplace_back(descriptor_sets[i].get(), i, 0, 1, vk::DescriptorType::eStorageTexelBuffer,
nullptr, &bindings.lut[i]);
bindings.lut_update[i] = false;
nullptr, &buffer_infos.back(), &bindings[i].buffer_view.get());
bindings[i].dirty = false;
}
}
}
@@ -248,4 +372,24 @@ void VulkanState::UpdateDescriptorSet() {
}
}
void VulkanState::CompileTrivialShader() {
auto source = OpenGL::GenerateTrivialVertexShader(true);
shaderc::Compiler compiler;
shaderc::CompileOptions options;
options.SetOptimizationLevel(shaderc_optimization_level_performance);
options.SetTargetEnvironment(shaderc_target_env_vulkan, shaderc_env_version_vulkan_1_2);
auto shader_module = compiler.CompileGlslToSpv(source.code, shaderc_glsl_vertex_shader, "vertex shader", options);
if (shader_module.GetCompilationStatus() != shaderc_compilation_status_success) {
LOG_CRITICAL(Render_Vulkan, shader_module.GetErrorMessage().c_str());
}
auto shader_code = std::vector<u32>{ shader_module.cbegin(), shader_module.cend() };
vk::ShaderModuleCreateInfo shader_info { {}, shader_code };
auto& device = g_vk_instace->GetDevice();
trivial_vertex_shader = device.createShaderModuleUnique(shader_info);
}
} // namespace Vulkan

View File

@@ -5,47 +5,51 @@
#pragma once
#include <array>
#include <variant>
#include "video_core/renderer_vulkan/vk_texture.h"
namespace Vulkan {
enum class DirtyState {
None,
Framebuffer,
Pipeline,
Texture,
Sampler,
TexelBuffer,
ImageTexture,
Depth,
Stencil,
LogicOp,
Viewport,
Scissor,
CullMode,
VertexBuffer,
IndexBuffer,
Uniform,
All
enum class DirtyFlags {
None = 0,
Framebuffer = 1,
Pipeline = 1 << 1,
Texture = 1 << 2,
Sampler = 1 << 3,
TexelBuffer = 1 << 4,
ImageTexture = 1 << 5,
DepthTest = 1 << 6,
Stencil = 1 << 7,
LogicOp = 1 << 8,
Viewport = 1 << 9,
Scissor = 1 << 10,
CullMode = 1 << 11,
VertexBuffer = 1 << 12,
IndexBuffer = 1 << 13,
Uniform = 1 << 14,
FrontFace = 1 << 15,
BlendConsts = 1 << 16,
ColorMask = 1 << 17,
StencilMask = 1 << 18,
DepthWrite = 1 << 19,
All = (1 << 20) - 1
};
enum class UniformID {
Pica = 0,
Shader = 1
enum class BindingID {
VertexUniform = 0,
PicaUniform = 1,
Tex0 = 2,
Tex1 = 3,
Tex2 = 4,
TexCube = 5,
LutLF = 6,
LutRG = 7,
LutRGBA = 8
};
enum class TextureID {
Tex0 = 0,
Tex1 = 1,
Tex2 = 2,
TexCube = 3
};
enum class TexelBufferID {
LF = 0,
RG = 1,
RGBA = 2
};
BindingID operator + (BindingID lhs, u32 rhs) {
return static_cast<BindingID>(static_cast<u32>(lhs) + rhs);
}
/// Tracks global Vulkan state
class VulkanState {
@@ -56,80 +60,98 @@ public:
/// Initialize object to its initial state
void Create();
/// Query state
bool DepthTestEnabled() const { return depth_enabled && depth_writes; }
bool StencilTestEnabled() const { return stencil_enabled && stencil_writes; }
/// Configure drawing state
void SetVertexBuffer(VKBuffer* 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(bool red, bool green, bool blue, bool alpha);
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 SetAttachments(VKTexture* color, VKTexture* depth_stencil);
void PushRenderTargets(VKTexture* color, VKTexture* depth_stencil);
void PopRenderTargets();
void SetRenderArea(vk::Rect2D render_area);
void BeginRendering();
void EndRendering();
/// Configure shader resources
void SetUniformBuffer(UniformID id, VKBuffer* buffer, u32 offset, u32 size);
void SetTexture(TextureID id, VKTexture* texture);
void SetTexelBuffer(TexelBufferID id, VKBuffer* buffer);
void SetUniformBuffer(BindingID id, VKBuffer* buffer, u32 offset, u32 size);
void SetTexture(BindingID id, VKTexture* texture);
void SetTexelBuffer(BindingID id, VKBuffer* buffer, vk::Format view_format);
void UnbindTexture(VKTexture* image);
void UnbindTexture(u32 index);
/// Apply all dirty state to the current Vulkan command buffer
void UpdateDescriptorSet();
void Apply();
private:
// Stage which should be applied
DirtyState dirty_flags;
void UpdateDescriptorSet();
void GetPipeline();
void CompileTrivialShader();
private:
struct Binding {
bool dirty{};
std::variant<VKBuffer*, VKTexture*> resource{};
vk::UniqueBufferView buffer_view{};
};
struct Attachment {
VKTexture* color{};
VKTexture* depth_stencil{};
};
DirtyFlags dirty_flags;
bool rendering = false;
VKTexture dummy_texture;
vk::UniqueSampler sampler;
// Input assembly
VKBuffer* vertex_buffer = nullptr, * index_buffer = nullptr;
vk::DeviceSize vertex_offset = 0, index_offset = 0;
// Shader bindings. These describe which resources
// we have bound to the pipeline and at which
// bind points. When the state is applied the
// descriptor sets are updated with the new
// resources
struct
{
std::array<vk::DescriptorBufferInfo, 2> ubo;
std::array<bool, 2> ubo_update;
std::array<vk::DescriptorImageInfo, 4> texture;
std::array<bool, 4> texture_update;
std::array<vk::DescriptorBufferInfo, 3> lut;
std::array<bool, 3> lut_update;
} bindings = {};
std::vector<vk::UniqueDescriptorSet> descriptor_sets = {};
VKBuffer* vertex_buffer{}, * index_buffer{};
vk::DeviceSize vertex_offset{}, index_offset{};
std::array<Binding, 9> bindings{};
std::vector<vk::UniqueDescriptorSet> descriptor_sets{};
vk::UniqueDescriptorPool desc_pool;
// Rasterization
vk::Viewport viewport = { 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f };
vk::CullModeFlags cull_mode = vk::CullModeFlagBits::eNone;
vk::Rect2D scissor = { {0, 0}, {1, 1} };
VKTexture dummy_texture;
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{};
// Render attachments
VKTexture* color_attachment = nullptr, * depth_attachment = nullptr;
vk::Rect2D render_area = {};
vk::ColorComponentFlags color_mask;
// Depth
bool depth_enabled;
VKTexture* color_attachment{}, * depth_attachment{};
vk::Rect2D render_area{};
bool depth_enabled, depth_writes;
vk::CompareOp test_func;
// Stencil
bool stencil_enabled;
vk::StencilFaceFlags face_mask;
vk::StencilOp fail_op, pass_op;
vk::StencilOp depth_fail_op;
u32 stencil_write_mask{}, stencil_input_mask{}, stencil_ref{};
bool stencil_enabled{}, stencil_writes{};
vk::StencilOp fail_op, pass_op, depth_fail_op;
vk::CompareOp compare_op;
vk::LogicOp logic_op;
std::array<bool, 2> clip_distance;
struct {
vk::PipelineColorBlendAttachmentState blend;
vk::PipelineDepthStencilStateCreateInfo depth_stencil;
} static_state;
// Pipeline cache
vk::UniqueShaderModule trivial_vertex_shader;
};
extern std::unique_ptr<VulkanState> g_vk_state;
} // namespace Vulkan

View File

@@ -10,6 +10,7 @@
#include "common/logging/log.h"
#include "video_core/renderer_vulkan/vk_swapchain.h"
#include "video_core/renderer_vulkan/vk_instance.h"
#include "video_core/renderer_vulkan/vk_resource_cache.h"
namespace Vulkan {
@@ -202,14 +203,24 @@ void VKSwapChain::SetupImages() {
);
// Wrap swapchain images with VKTexture
swapchain_images[i].image.Adopt(images[i], color_attachment_view);
swapchain_images[i].image = images[i];
swapchain_images[i].image_view = device.createImageViewUnique(color_attachment_view);
// Create framebuffer for each swapchain image
VKFramebuffer::Info fb_info = {
.color = &swapchain_images[i].image
};
vk::FramebufferCreateInfo framebuffer_info
(
{},
g_vk_res_cache->GetRenderPass(details.format.format,
vk::Format::eUndefined,
vk::SampleCountFlagBits::e1,
vk::AttachmentLoadOp::eLoad),
{},
details.extent.width,
details.extent.height,
1
);
swapchain_images[i].framebuffer.Create(fb_info);
swapchain_images[i].framebuffer = device.createFramebufferUnique(framebuffer_info);
}
}

View File

@@ -11,8 +11,9 @@
namespace Vulkan {
struct SwapChainImage {
VKTexture image;
VKFramebuffer framebuffer;
vk::Image image;
vk::UniqueImageView image_view;
vk::UniqueFramebuffer framebuffer;
};
struct SwapChainDetails {
@@ -49,8 +50,8 @@ public:
vk::SwapchainKHR GetSwapChain() const { return swapchain.get(); }
/// Retrieve current texture and framebuffer
VKTexture& GetCurrentImage() { return swapchain_images[image_index].image; }
VKFramebuffer& GetCurrentFramebuffer() { return swapchain_images[image_index].framebuffer; }
vk::Image GetCurrentImage() { return swapchain_images[image_index].image; }
vk::Framebuffer GetCurrentFramebuffer() { return swapchain_images[image_index].framebuffer.get(); }
private:
void PopulateSwapchainDetails(vk::SurfaceKHR surface, u32 width, u32 height);

View File

@@ -9,17 +9,24 @@
namespace Vulkan {
VKTaskScheduler::VKTaskScheduler(VKSwapChain* swapchain) : swapchain(swapchain) {
}
VKTaskScheduler::VKTaskScheduler(VKSwapChain* swapchain) :
swapchain(swapchain) {}
VKTaskScheduler::~VKTaskScheduler() {
// Sync the GPU before exiting
SyncToGPU();
}
vk::CommandBuffer VKTaskScheduler::GetCommandBuffer() {
return tasks[current_task].command_buffer;
std::tuple<u8*, u32> VKTaskScheduler::RequestStaging(u32 size) {
auto& task = tasks[current_task];
if (size > STAGING_BUFFER_SIZE - task.current_offset) {
return std::make_tuple(nullptr, 0);
}
u8* ptr = task.staging.GetHostPointer() + task.current_offset;
auto result = std::make_tuple(ptr, task.current_offset);
task.current_offset += size;
return result;
}
bool VKTaskScheduler::Create() {
@@ -36,7 +43,13 @@ bool VKTaskScheduler::Create() {
timeline = device.createSemaphoreUnique(semaphore_info);
// Initialize task structures
VKBuffer::Info staging_info = {
.size = STAGING_BUFFER_SIZE,
.properties = vk::MemoryPropertyFlagBits::eHostVisible |
vk::MemoryPropertyFlagBits::eHostCoherent,
.usage = vk::BufferUsageFlagBits::eTransferSrc
};
for (auto& task : tasks) {
// Create command buffers
vk::CommandBufferAllocateInfo buffer_info
@@ -47,6 +60,9 @@ bool VKTaskScheduler::Create() {
);
task.command_buffer = device.allocateCommandBuffers(buffer_info)[0];
// Create staging buffer
task.staging.Create(staging_info);
}
// Create present semaphore
@@ -79,8 +95,8 @@ void VKTaskScheduler::SyncToGPU(u64 task_index) {
// Delete all resources that can be freed now
for (auto& task : tasks) {
if (task.task_id > old_gpu_tick && task.task_id <= new_gpu_tick) {
for (auto& deleter : task.cleanups) {
deleter();
for (auto& func : task.cleanups) {
func();
}
}
}
@@ -139,17 +155,13 @@ void VKTaskScheduler::BeginTask() {
// Wait for the GPU to finish with all resources for this task.
SyncToGPU(next_task_index);
// Reset command pools to beginning since we can re-use the memory now
device.resetCommandPool(command_pool.get());
vk::CommandBufferBeginInfo begin_info(vk::CommandBufferUsageFlagBits::eOneTimeSubmit);
// Enable commands to be recorded to the command buffer again.
task.command_buffer.begin(begin_info);
task.command_buffer.begin({vk::CommandBufferUsageFlagBits::eOneTimeSubmit});
// Reset upload command buffer state
current_task = next_task_index;
task.task_id = current_task_id++;
task.current_offset = 0;
}
std::unique_ptr<VKTaskScheduler> g_vk_task_scheduler;

View File

@@ -17,19 +17,20 @@
#include "common/blocking_loop.h"
#include "common/threadsafe_queue.h"
#include "video_core/renderer_vulkan/vk_instance.h"
#include "video_core/renderer_vulkan/vk_buffer.h"
namespace Vulkan {
/// Number of tasks that can be submitted concurrently. This allows the host
/// to start recording the next frame while the GPU is working on the
/// current one. Larger values can be used with caution, as they can cause
/// frame latency if the CPU is too far ahead of the GPU
constexpr u32 CONCURRENT_TASK_COUNT = 2;
constexpr u32 STAGING_BUFFER_SIZE = 16 * 1024 * 1024;
class VKSwapChain;
/// Wrapper class around command buffer execution.
/// Handles synchronization and submission of command buffers
/// Wrapper class around command buffer execution. Handles an arbitrary
/// number of tasks that can be submitted concurrently. This allows the host
/// to start recording the next frame while the GPU is working on the
/// current one. Larger values can be used with caution, as they can cause
/// frame latency if the CPU is too far ahead of the GPU
class VKTaskScheduler {
public:
explicit VKTaskScheduler(VKSwapChain* swapchain);
@@ -39,7 +40,9 @@ public:
bool Create();
/// Retrieve either of the current frame's command buffers
vk::CommandBuffer GetCommandBuffer();
vk::CommandBuffer GetCommandBuffer() const { return tasks[current_task].command_buffer; }
VKBuffer& GetStaging() { return tasks[current_task].staging; }
std::tuple<u8*, u32> RequestStaging(u32 size);
/// Returns the task id that the CPU is recording
u64 GetCPUTick() const { return current_task_id; }
@@ -62,9 +65,11 @@ private:
private:
struct Task {
u64 task_id{};
std::vector<std::function<void()>> cleanups;
vk::CommandBuffer command_buffer;
u64 task_id = 0;
VKBuffer staging;
u32 current_offset{};
};
vk::UniqueSemaphore timeline;

View File

@@ -12,32 +12,28 @@
namespace Vulkan {
VKTexture::~VKTexture() {
// Make sure to unbind the texture before destroying it
g_vk_state->UnbindTexture(this);
if (texture) {
// Make sure to unbind the texture before destroying it
g_vk_state->UnbindTexture(this);
auto deleter = [this]() {
auto& device = g_vk_instace->GetDevice();
auto deleter = [this]() {
auto& device = g_vk_instace->GetDevice();
device.destroyImage(texture);
device.destroyImageView(view);
device.freeMemory(memory);
};
if (texture) {
if (cleanup_image) {
device.destroyImage(texture);
}
device.destroyImageView(texture_view);
device.freeMemory(texture_memory);
}
};
// Schedule deletion of the texture after it's no longer used
// by the GPU
g_vk_task_scheduler->Schedule(deleter);
// Schedule deletion of the texture after it's no longer used
// by the GPU
g_vk_task_scheduler->Schedule(deleter);
}
}
void VKTexture::Create(const Info& info, bool make_staging) {
void VKTexture::Create(const VKTexture::Info& create_info) {
auto& device = g_vk_instace->GetDevice();
texture_info = info;
info = create_info;
switch (texture_info.format)
switch (info.format)
{
case vk::Format::eR8G8B8A8Uint:
case vk::Format::eR8G8B8A8Srgb:
@@ -48,26 +44,24 @@ void VKTexture::Create(const Info& info, bool make_staging) {
channels = 3;
break;
default:
LOG_CRITICAL(Render_Vulkan, "Unknown texture format {}", texture_info.format);
LOG_CRITICAL(Render_Vulkan, "Unknown texture format {}", info.format);
}
// Create the texture
u32 image_size = texture_info.width * texture_info.height * channels;
vk::ImageCreateFlags flags;
image_size = info.width * info.height * channels;
vk::ImageCreateFlags flags{};
if (info.view_type == vk::ImageViewType::eCube) {
flags = vk::ImageCreateFlagBits::eCubeCompatible;
}
vk::ImageCreateInfo image_info
(
flags,
info.type,
texture_info.format,
{ texture_info.width, texture_info.height, 1 }, info.mipmap_levels, info.array_layers,
vk::ImageCreateInfo image_info {
flags, info.type, info.format,
{ info.width, info.height, 1 }, info.levels, info.layers,
static_cast<vk::SampleCountFlagBits>(info.multisamples),
vk::ImageTiling::eOptimal,
vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled
);
};
texture = device.createImage(image_info);
@@ -76,31 +70,23 @@ void VKTexture::Create(const Info& info, bool make_staging) {
auto memory_index = VKBuffer::FindMemoryType(requirements.memoryTypeBits, vk::MemoryPropertyFlagBits::eDeviceLocal);
vk::MemoryAllocateInfo alloc_info(requirements.size, memory_index);
texture_memory = device.allocateMemory(alloc_info);
device.bindImageMemory(texture, texture_memory, 0);
memory = device.allocateMemory(alloc_info);
device.bindImageMemory(texture, memory, 0);
// Create texture view
vk::ImageViewCreateInfo view_info({}, texture, info.view_type, texture_info.format, {},
vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1));
texture_view = device.createImageView(view_info);
vk::ImageViewCreateInfo view_info {
{}, texture, info.view_type, info.format, {},
{info.aspect, 0, info.levels, 0, info.levels}
};
// Create staging buffer
if (make_staging) {
staging.Create(image_size, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent,
vk::BufferUsageFlagBits::eTransferSrc);
view = device.createImageView(view_info);
}
void VKTexture::Transition(vk::ImageLayout new_layout) {
if (new_layout == layout) {
return;
}
}
void VKTexture::Adopt(vk::Image image, vk::ImageViewCreateInfo view_info) {
// Prevent image cleanup at object destruction
cleanup_image = false;
texture = image;
// Create image view
texture_view = g_vk_instace->GetDevice().createImageView(view_info);
}
void VKTexture::TransitionLayout(vk::ImageLayout new_layout, vk::CommandBuffer command_buffer) {
struct LayoutInfo {
vk::ImageLayout layout;
vk::AccessFlags access;
@@ -110,7 +96,7 @@ void VKTexture::TransitionLayout(vk::ImageLayout new_layout, vk::CommandBuffer c
// Get optimal transition settings for every image layout. Settings taken from Dolphin
auto layout_info = [&](vk::ImageLayout layout) -> LayoutInfo {
LayoutInfo info = { .layout = layout };
switch (texture_layout) {
switch (layout) {
case vk::ImageLayout::eUndefined:
// Layout undefined therefore contents undefined, and we don't care what happens to it.
info.access = vk::AccessFlagBits::eNone;
@@ -154,7 +140,7 @@ void VKTexture::TransitionLayout(vk::ImageLayout new_layout, vk::CommandBuffer c
break;
default:
LOG_CRITICAL(Render_Vulkan, "Unhandled vulkan image layout {}\n", texture_layout);
LOG_CRITICAL(Render_Vulkan, "Unhandled vulkan image layout {}\n", layout);
break;
}
@@ -162,112 +148,47 @@ void VKTexture::TransitionLayout(vk::ImageLayout new_layout, vk::CommandBuffer c
};
// Submit pipeline barrier
LayoutInfo source = layout_info(texture_layout), dst = layout_info(new_layout);
vk::ImageMemoryBarrier barrier
(
LayoutInfo source = layout_info(layout), dst = layout_info(new_layout);
vk::ImageMemoryBarrier barrier {
source.access, dst.access,
source.layout, dst.layout,
VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED,
texture,
vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1)
);
std::array<vk::ImageMemoryBarrier, 1> barriers = { barrier };
vk::ImageSubresourceRange(info.aspect, 0, 1, 0, 1)
};
std::array<vk::ImageMemoryBarrier, 1> barriers{ barrier };
auto command_buffer = g_vk_task_scheduler->GetCommandBuffer();
command_buffer.pipelineBarrier(source.stage, dst.stage, vk::DependencyFlagBits::eByRegion, {}, {}, barriers);
vk::SubmitInfo submit_info({}, {}, {}, 1, &command_buffer);
// Update texture layout
texture_layout = new_layout;
layout = new_layout;
}
void VKTexture::CopyPixels(std::span<u32> new_pixels) {
if (!staging.GetHostPointer()) {
void VKTexture::Upload(u32 level, u32 layer, u32 row_length, vk::Rect2D region, std::span<u8> pixels) {
u8* staging = g_vk_task_scheduler->RequestStaging(pixels.size());
if (!staging) {
LOG_ERROR(Render_Vulkan, "Cannot copy pixels without staging buffer!");
}
auto command_buffer = g_vk_task_scheduler->GetCommandBuffer();
// Copy pixels to staging buffer
std::memcpy(staging.GetHostPointer(),
new_pixels.data(), new_pixels.size() * channels);
std::memcpy(staging, pixels.data(), pixels.size());
vk::BufferImageCopy region(0, 0, 0, vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, 0, 0, 1), 0,
{ texture_info.width, texture_info.height, 1 });
std::array<vk::BufferImageCopy, 1> regions = { region };
vk::BufferImageCopy copy_region {
0, row_length, region.extent.height,
{info.aspect, level, layer, 1},
{ region.offset.x, region.offset.y, 0 },
{ region.extent.width, region.extent.height, 1 }
};
// Transition image to transfer format
TransitionLayout(vk::ImageLayout::eTransferDstOptimal, command_buffer);
command_buffer.copyBufferToImage(staging.GetBuffer(), texture, vk::ImageLayout::eTransferDstOptimal, regions);
Transition(vk::ImageLayout::eTransferDstOptimal);
command_buffer.copyBufferToImage(g_vk_task_scheduler->GetStaging().GetBuffer(),
texture, vk::ImageLayout::eTransferDstOptimal,
copy_region);
// Prepare for shader reads
TransitionLayout(vk::ImageLayout::eShaderReadOnlyOptimal, command_buffer);
}
void VKTexture::BlitTo(Common::Rectangle<u32> srect, VKTexture* dest,
Common::Rectangle<u32> drect, SurfaceParams::SurfaceType type) {
auto command_buffer = g_vk_task_scheduler->GetCommandBuffer();
// Ensure textures are of the same dimention
assert(texture_info.width == dest->texture_info.width &&
texture_info.height == dest->texture_info.height);
vk::ImageAspectFlags image_aspect;
switch (type) {
case SurfaceParams::SurfaceType::Color:
case SurfaceParams::SurfaceType::Texture:
image_aspect = vk::ImageAspectFlagBits::eColor;
break;
case SurfaceParams::SurfaceType::Depth:
image_aspect = vk::ImageAspectFlagBits::eDepth;
break;
case SurfaceParams::SurfaceType::DepthStencil:
image_aspect = vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil;
break;
default:
LOG_CRITICAL(Render_Vulkan, "Unhandled image blit aspect\n");
UNREACHABLE();
}
// Define the region to blit
vk::ImageSubresourceLayers layers(image_aspect, 0, 0, 1);
std::array<vk::Offset3D, 2> src_offsets = { vk::Offset3D(srect.left, srect.bottom, 1), vk::Offset3D(srect.right, srect.top, 1) };
std::array<vk::Offset3D, 2> dst_offsets = { vk::Offset3D(drect.left, drect.bottom, 1), vk::Offset3D(drect.right, drect.top, 1) };
std::array<vk::ImageBlit, 1> regions = {{{layers, src_offsets, layers, dst_offsets}}};
// Transition image layouts
TransitionLayout(vk::ImageLayout::eTransferSrcOptimal, command_buffer);
dest->TransitionLayout(vk::ImageLayout::eTransferDstOptimal, command_buffer);
// Perform blit operation
command_buffer.blitImage(texture, vk::ImageLayout::eTransferSrcOptimal, dest->GetHandle(),
vk::ImageLayout::eTransferDstOptimal, regions, vk::Filter::eNearest);
}
void VKTexture::Fill(Common::Rectangle<u32> region, vk::ImageAspectFlags aspect,
vk::ClearValue value) {
auto command_buffer = g_vk_task_scheduler->GetCommandBuffer();
TransitionLayout(vk::ImageLayout::eTransferDstOptimal, command_buffer);
// End any ongoing rendering operations
g_vk_state->EndRendering();
// Set fill area
g_vk_state->SetAttachments(this, nullptr);
// Begin clear render
g_vk_state->BeginRendering();
vk::Offset2D offset(region.left, region.bottom);
vk::Rect2D rect(offset, { region.GetWidth(), region.GetHeight() });
vk::ClearAttachment clear_info(aspect, 0, value);
vk::ClearRect clear_rect(rect, 0, 1);
command_buffer.clearAttachments(clear_info, clear_rect);
TransitionLayout(vk::ImageLayout::eShaderReadOnlyOptimal, command_buffer);
Transition(vk::ImageLayout::eShaderReadOnlyOptimal);
}
}

View File

@@ -15,15 +15,13 @@
namespace Vulkan {
struct SamplerInfo {
std::array<vk::SamplerAddressMode, 3> wrapping = { vk::SamplerAddressMode::eClampToEdge };
vk::Filter min_filter = vk::Filter::eLinear;
vk::Filter mag_filter = vk::Filter::eLinear;
vk::SamplerMipmapMode mipmap_mode = vk::SamplerMipmapMode::eLinear;
std::array<vk::SamplerAddressMode, 3> wrapping{};
vk::Filter min_filter{}, mag_filter{};
vk::SamplerMipmapMode mipmap_mode{};
};
/// Vulkan texture object
class VKTexture final : public NonCopyable {
friend class VKFramebuffer;
public:
/// Information for the creation of the target texture
struct Info {
@@ -31,57 +29,40 @@ public:
vk::Format format;
vk::ImageType type;
vk::ImageViewType view_type;
u32 mipmap_levels = 1;
u32 array_layers = 1;
vk::ImageUsageFlags usage;
vk::ImageAspectFlags aspect;
u32 multisamples = 1;
u32 levels = 1, layers = 1;
SamplerInfo sampler_info = {};
};
VKTexture() = default;
VKTexture(VKTexture&&) = default;
~VKTexture();
/// Create a new Vulkan texture object
void Create(const Info& info, bool staging = false);
void Create(const VKTexture::Info& info);
/// Create a non-owning texture object, usefull for image object
/// from the swapchain that are managed by another object
void Adopt(vk::Image image, vk::ImageViewCreateInfo view_info);
/// Query objects
bool IsValid() const { return texture; }
vk::Image GetHandle() const { return texture; }
vk::ImageView GetView() const { return view; }
vk::Format GetFormat() const { return info.format; }
vk::ImageLayout GetLayout() const { return layout; }
u32 GetSamples() const { return info.multisamples; }
/// Copies CPU side pixel data to the GPU texture buffer
void CopyPixels(std::span<u32> pixels);
/// Get Vulkan objects
vk::Image GetHandle() const { return texture; }
vk::ImageView GetView() const { return texture_view; }
vk::Format GetFormat() const { return texture_info.format; }
vk::Rect2D GetRect() const { return vk::Rect2D({}, { texture_info.width, texture_info.height }); }
vk::ImageLayout GetLayout() const { return texture_layout; }
u32 GetSamples() const { return texture_info.multisamples; }
bool IsValid() { return texture; }
void Upload(u32 level, u32 layer, u32 row_length, vk::Rect2D region, std::span<u8> pixels);
/// Used to transition the image to an optimal layout during transfers
void TransitionLayout(vk::ImageLayout new_layout, vk::CommandBuffer command_buffer);
/// Fill the texture with the values provided
void Fill(Common::Rectangle<u32> region, vk::ImageAspectFlags aspect,
vk::ClearValue value);
/// Copy current texture to another with optionally performing format convesions
void BlitTo(Common::Rectangle<u32> srect, VKTexture* dest,
Common::Rectangle<u32> drect, SurfaceParams::SurfaceType type);
void Transition(vk::ImageLayout new_layout);
private:
bool cleanup_image = true;
Info texture_info;
vk::ImageLayout texture_layout = vk::ImageLayout::eUndefined;
VKTexture::Info info{};
vk::ImageLayout layout{};
vk::Image texture;
vk::ImageView texture_view;
vk::DeviceMemory texture_memory;
u32 channels;
// TODO: Make a global staging buffer
VKBuffer staging;
vk::ImageView view;
vk::DeviceMemory memory;
u32 channels{}, image_size{};
};
} // namespace Vulkan