video_core: Initial Vulkan renderer dump
* This is the basic skeleton of the new Citra Vulkan renderer. It's based on vulkan-hpp and my own wrapper library which I wrote for my some of my projects. Currently it doesn't even compile and the code is less than adequate.
This commit is contained in:
@ -24,6 +24,8 @@ option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON)
|
||||
|
||||
option(ENABLE_CUBEB "Enables the cubeb audio backend" ON)
|
||||
|
||||
option(ENABLE_VULKAN "Enables the use of the Vulkan API" ON)
|
||||
|
||||
option(ENABLE_FFMPEG_AUDIO_DECODER "Enable FFmpeg audio (AAC) decoder" OFF)
|
||||
option(ENABLE_FFMPEG_VIDEO_DUMPER "Enable FFmpeg video dumper" OFF)
|
||||
|
||||
|
@ -102,6 +102,7 @@ enum class Class : ClassType {
|
||||
Render, ///< Emulator video output and hardware acceleration
|
||||
Render_Software, ///< Software renderer backend
|
||||
Render_OpenGL, ///< OpenGL backend
|
||||
Render_Vulkan, ///< Vulkan backend
|
||||
Audio, ///< Audio emulation
|
||||
Audio_DSP, ///< The HLE and LLE implementations of the DSP
|
||||
Audio_Sink, ///< Emulator audio output backend
|
||||
|
@ -68,8 +68,21 @@ add_library(video_core STATIC
|
||||
renderer_opengl/texture_filters/xbrz/xbrz_freescale.cpp
|
||||
renderer_opengl/texture_filters/xbrz/xbrz_freescale.h
|
||||
#temporary, move these back in alphabetical order before merging
|
||||
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_buffer.cpp
|
||||
renderer_vulkan/vk_buffer.h
|
||||
renderer_vulkan/vk_context.cpp
|
||||
renderer_vulkan/vk_context.h
|
||||
renderer_vulkan/vk_resource_manager.cpp
|
||||
renderer_vulkan/vk_resource_manager.h
|
||||
renderer_vulkan/vk_state.cpp
|
||||
renderer_vulkan/vk_state.h
|
||||
renderer_vulkan/vk_swapchain.cpp
|
||||
renderer_vulkan/vk_swapchain.h
|
||||
renderer_vulkan/vk_texture.cpp
|
||||
renderer_vulkan/vk_texture.h
|
||||
shader/debug_data.h
|
||||
shader/shader.cpp
|
||||
shader/shader.h
|
||||
@ -142,6 +155,12 @@ if(ARCHITECTURE_x86_64)
|
||||
)
|
||||
endif()
|
||||
|
||||
if (ENABLE_VULKAN)
|
||||
# Find the VulkanSDK
|
||||
find_package(Vulkan REQUIRED)
|
||||
find_library(SHADERC_LIB shaderc shaderc_shared)
|
||||
endif()
|
||||
|
||||
create_target_directory_groups(video_core)
|
||||
|
||||
target_link_libraries(video_core PUBLIC common core)
|
||||
@ -150,3 +169,8 @@ target_link_libraries(video_core PRIVATE glad nihstro-headers Boost::serializati
|
||||
if (ARCHITECTURE_x86_64)
|
||||
target_link_libraries(video_core PUBLIC xbyak)
|
||||
endif()
|
||||
|
||||
if (ENABLE_VULKAN)
|
||||
target_include_directories(video_core PRIVATE ${Vulkan_INCLUDE_DIRS})
|
||||
target_link_libraries(video_core PRIVATE ${Vulkan_LIBRARIES} ${SHADERC_LIB})
|
||||
endif()
|
||||
|
224
src/video_core/renderer_vulkan/pica_to_vulkan.h
Normal file
224
src/video_core/renderer_vulkan/pica_to_vulkan.h
Normal file
@ -0,0 +1,224 @@
|
||||
// Copyright 2015 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <glad/glad.h>
|
||||
#include <glm/glm.hpp>
|
||||
#include <vulkan/vulkan.hpp>
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "video_core/regs_framebuffer.h"
|
||||
#include "video_core/regs_lighting.h"
|
||||
#include "video_core/regs_texturing.h"
|
||||
|
||||
namespace PicaToVK {
|
||||
|
||||
using TextureFilter = Pica::TexturingRegs::TextureConfig::TextureFilter;
|
||||
|
||||
struct FilterInfo {
|
||||
vk::Filter mag_filter, min_filter;
|
||||
vk::SamplerMipmapMode mip_mode;
|
||||
};
|
||||
|
||||
inline FilterInfo TextureFilterMode(TextureFilter mag, TextureFilter min, TextureFilter mip) {
|
||||
std::array<vk::Filter, 2> filter_table = { vk::Filter::eNearest, vk::Filter::eLinear };
|
||||
std::array<vk::SamplerMipmapMode, 2> mipmap_table = { vk::SamplerMipmapMode::eNearest, vk::SamplerMipmapMode::eLinear };
|
||||
|
||||
return FilterInfo{filter_table[mag], filter_table[min], mipmap_table[mip]};
|
||||
}
|
||||
|
||||
inline vk::SamplerAddressMode WrapMode(Pica::TexturingRegs::TextureConfig::WrapMode mode) {
|
||||
static constexpr std::array<vk::SamplerAddressMode, 8> wrap_mode_table{{
|
||||
vk::SamplerAddressMode::eClampToEdge,
|
||||
vk::SamplerAddressMode::eClampToBorder,
|
||||
vk::SamplerAddressMode::eRepeat,
|
||||
vk::SamplerAddressMode::eMirroredRepeat,
|
||||
// TODO(wwylele): ClampToEdge2 and ClampToBorder2 are not properly implemented here. See the
|
||||
// comments in enum WrapMode.
|
||||
vk::SamplerAddressMode::eClampToEdge,
|
||||
vk::SamplerAddressMode::eClampToBorder,
|
||||
vk::SamplerAddressMode::eRepeat,
|
||||
vk::SamplerAddressMode::eRepeat,
|
||||
}};
|
||||
|
||||
const auto index = static_cast<std::size_t>(mode);
|
||||
|
||||
// Range check table for input
|
||||
if (index >= wrap_mode_table.size()) {
|
||||
LOG_CRITICAL(Render_Vulkan, "Unknown texture wrap mode {}", index);
|
||||
UNREACHABLE();
|
||||
|
||||
return vk::SamplerAddressMode::eClampToEdge;
|
||||
}
|
||||
|
||||
if (index > 3) {
|
||||
Core::System::GetInstance().TelemetrySession().AddField(
|
||||
Common::Telemetry::FieldType::Session, "VideoCore_Pica_UnsupportedTextureWrapMode",
|
||||
static_cast<u32>(index));
|
||||
LOG_WARNING(Render_Vulkan, "Using texture wrap mode {}", index);
|
||||
}
|
||||
|
||||
return wrap_mode_table[index];
|
||||
}
|
||||
|
||||
inline vk::BlendOp BlendEquation(Pica::FramebufferRegs::BlendEquation equation) {
|
||||
static constexpr std::array<vk::BlendOp, 5> blend_equation_table{{
|
||||
vk::BlendOp::eAdd,
|
||||
vk::BlendOp::eSubtract,
|
||||
vk::BlendOp::eReverseSubtract,
|
||||
vk::BlendOp::eMin,
|
||||
vk::BlendOp::eMax,
|
||||
}};
|
||||
|
||||
const auto index = static_cast<std::size_t>(equation);
|
||||
|
||||
// Range check table for input
|
||||
if (index >= blend_equation_table.size()) {
|
||||
LOG_CRITICAL(Render_Vulkan, "Unknown blend equation {}", index);
|
||||
|
||||
// This return value is hwtested, not just a stub
|
||||
return vk::BlendOp::eAdd;
|
||||
}
|
||||
|
||||
return blend_equation_table[index];
|
||||
}
|
||||
|
||||
inline vk::BlendFactor BlendFunc(Pica::FramebufferRegs::BlendFactor factor) {
|
||||
static constexpr std::array<vk::BlendFactor, 15> blend_func_table{{
|
||||
vk::BlendFactor::eZero, // BlendFactor::Zero
|
||||
vk::BlendFactor::eOne, // BlendFactor::One
|
||||
vk::BlendFactor::eSrcColor, // BlendFactor::SourceColor
|
||||
vk::BlendFactor::eOneMinusSrcColor, // BlendFactor::OneMinusSourceColor
|
||||
vk::BlendFactor::eDstColor, // BlendFactor::DestColor
|
||||
vk::BlendFactor::eOneMinusDstColor, // BlendFactor::OneMinusDestColor
|
||||
vk::BlendFactor::eSrcAlpha, // BlendFactor::SourceAlpha
|
||||
vk::BlendFactor::eOneMinusSrcAlpha, // BlendFactor::OneMinusSourceAlpha
|
||||
vk::BlendFactor::eDstAlpha, // BlendFactor::DestAlpha
|
||||
vk::BlendFactor::eOneMinusDstAlpha, // BlendFactor::OneMinusDestAlpha
|
||||
vk::BlendFactor::eConstantColor, // BlendFactor::ConstantColor
|
||||
vk::BlendFactor::eOneMinusConstantColor,// BlendFactor::OneMinusConstantColor
|
||||
vk::BlendFactor::eConstantAlpha, // BlendFactor::ConstantAlpha
|
||||
vk::BlendFactor::eOneMinusConstantAlpha,// BlendFactor::OneMinusConstantAlpha
|
||||
vk::BlendFactor::eSrcAlphaSaturate, // BlendFactor::SourceAlphaSaturate
|
||||
}};
|
||||
|
||||
const auto index = static_cast<std::size_t>(factor);
|
||||
|
||||
// Range check table for input
|
||||
if (index >= blend_func_table.size()) {
|
||||
LOG_CRITICAL(Render_Vulkan, "Unknown blend factor {}", index);
|
||||
UNREACHABLE();
|
||||
|
||||
return vk::BlendFactor::eOne;
|
||||
}
|
||||
|
||||
return blend_func_table[index];
|
||||
}
|
||||
|
||||
inline vk::LogicOp LogicOp(Pica::FramebufferRegs::LogicOp op) {
|
||||
static constexpr std::array<vk::LogicOp, 16> logic_op_table{{
|
||||
vk::LogicOp::eClear, // Clear
|
||||
vk::LogicOp::eAnd, // And
|
||||
vk::LogicOp::eAndReverse, // AndReverse
|
||||
vk::LogicOp::eCopy, // Copy
|
||||
vk::LogicOp::eSet, // Set
|
||||
vk::LogicOp::eCopyInverted, // CopyInverted
|
||||
vk::LogicOp::eNoOp, // NoOp
|
||||
vk::LogicOp::eInvert, // Invert
|
||||
vk::LogicOp::eNand, // Nand
|
||||
vk::LogicOp::eOr, // Or
|
||||
vk::LogicOp::eNor, // Nor
|
||||
vk::LogicOp::eXor, // Xor
|
||||
vk::LogicOp::eEquivalent, // Equiv
|
||||
vk::LogicOp::eAndInverted, // AndInverted
|
||||
vk::LogicOp::eOrReverse, // OrReverse
|
||||
vk::LogicOp::eOrInverted, // OrInverted
|
||||
}};
|
||||
|
||||
const auto index = static_cast<std::size_t>(op);
|
||||
|
||||
// Range check table for input
|
||||
if (index >= logic_op_table.size()) {
|
||||
LOG_CRITICAL(Render_Vulkan, "Unknown logic op {}", index);
|
||||
UNREACHABLE();
|
||||
|
||||
return vk::LogicOp::eCopy;
|
||||
}
|
||||
|
||||
return logic_op_table[index];
|
||||
}
|
||||
|
||||
inline vk::CompareOp CompareFunc(Pica::FramebufferRegs::CompareFunc func) {
|
||||
static constexpr std::array<vk::CompareOp, 8> compare_func_table{{
|
||||
vk::CompareOp::eNever, // CompareFunc::Never
|
||||
vk::CompareOp::eAlways, // CompareFunc::Always
|
||||
vk::CompareOp::eEqual, // CompareFunc::Equal
|
||||
vk::CompareOp::eNotEqual, // CompareFunc::NotEqual
|
||||
vk::CompareOp::eLess, // CompareFunc::LessThan
|
||||
vk::CompareOp::eLessOrEqual, // CompareFunc::LessThanOrEqual
|
||||
vk::CompareOp::eGreater, // CompareFunc::GreaterThan
|
||||
vk::CompareOp::eGreaterOrEqual, // CompareFunc::GreaterThanOrEqual
|
||||
}};
|
||||
|
||||
const auto index = static_cast<std::size_t>(func);
|
||||
|
||||
// Range check table for input
|
||||
if (index >= compare_func_table.size()) {
|
||||
LOG_CRITICAL(Render_Vulkan, "Unknown compare function {}", index);
|
||||
UNREACHABLE();
|
||||
|
||||
return vk::CompareOp::eAlways;
|
||||
}
|
||||
|
||||
return compare_func_table[index];
|
||||
}
|
||||
|
||||
inline vk::StencilOp StencilOp(Pica::FramebufferRegs::StencilAction action) {
|
||||
static constexpr std::array<vk::StencilOp, 8> stencil_op_table{{
|
||||
vk::StencilOp::eKeep, // StencilAction::Keep
|
||||
vk::StencilOp::eZero, // StencilAction::Zero
|
||||
vk::StencilOp::eReplace, // StencilAction::Replace
|
||||
vk::StencilOp::eIncrementAndClamp, // StencilAction::Increment
|
||||
vk::StencilOp::eDecrementAndClamp, // StencilAction::Decrement
|
||||
vk::StencilOp::eInvert, // StencilAction::Invert
|
||||
vk::StencilOp::eIncrementAndWrap, // StencilAction::IncrementWrap
|
||||
vk::StencilOp::eDecrementAndWrap, // StencilAction::DecrementWrap
|
||||
}};
|
||||
|
||||
const auto index = static_cast<std::size_t>(action);
|
||||
|
||||
// Range check table for input
|
||||
if (index >= stencil_op_table.size()) {
|
||||
LOG_CRITICAL(Render_Vulkan, "Unknown stencil op {}", index);
|
||||
UNREACHABLE();
|
||||
|
||||
return vk::StencilOp::eKeep;
|
||||
}
|
||||
|
||||
return stencil_op_table[index];
|
||||
}
|
||||
|
||||
inline glm::vec4 ColorRGBA8(const u32 color) {
|
||||
return glm::vec4{
|
||||
(color >> 0 & 0xFF) / 255.0f,
|
||||
(color >> 8 & 0xFF) / 255.0f,
|
||||
(color >> 16 & 0xFF) / 255.0f,
|
||||
(color >> 24 & 0xFF) / 255.0f,
|
||||
};
|
||||
}
|
||||
|
||||
inline glm::vec3 LightColor(const Pica::LightingRegs::LightColor& color) {
|
||||
return glm::vec3{
|
||||
color.r / 255.0f,
|
||||
color.g / 255.0f,
|
||||
color.b / 255.0f,
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace PicaToGL
|
1289
src/video_core/renderer_vulkan/renderer_vulkan.cpp
Normal file
1289
src/video_core/renderer_vulkan/renderer_vulkan.cpp
Normal file
File diff suppressed because it is too large
Load Diff
138
src/video_core/renderer_vulkan/renderer_vulkan.h
Normal file
138
src/video_core/renderer_vulkan/renderer_vulkan.h
Normal file
@ -0,0 +1,138 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include "common/common_types.h"
|
||||
#include "common/math_util.h"
|
||||
#include "core/hw/gpu.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/renderer_opengl/frame_dumper_opengl.h"
|
||||
#include "video_core/renderer_opengl/gl_resource_manager.h"
|
||||
#include "video_core/renderer_opengl/gl_state.h"
|
||||
|
||||
namespace Layout {
|
||||
struct FramebufferLayout;
|
||||
}
|
||||
|
||||
namespace Frontend {
|
||||
|
||||
struct Frame {
|
||||
u32 width{}; /// Width of the frame (to detect resize)
|
||||
u32 height{}; /// Height of the frame
|
||||
bool color_reloaded = false; /// Texture attachment was recreated (ie: resized)
|
||||
OpenGL::OGLRenderbuffer color{}; /// Buffer shared between the render/present FBO
|
||||
OpenGL::OGLFramebuffer render{}; /// FBO created on the render thread
|
||||
OpenGL::OGLFramebuffer present{}; /// FBO created on the present thread
|
||||
GLsync render_fence{}; /// Fence created on the render thread
|
||||
GLsync present_fence{}; /// Fence created on the presentation thread
|
||||
};
|
||||
} // namespace Frontend
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
/// Structure used for storing information about the textures for each 3DS screen
|
||||
struct TextureInfo {
|
||||
OGLTexture resource;
|
||||
GLsizei width;
|
||||
GLsizei height;
|
||||
GPU::Regs::PixelFormat format;
|
||||
GLenum gl_format;
|
||||
GLenum gl_type;
|
||||
};
|
||||
|
||||
/// Structure used for storing information about the display target for each 3DS screen
|
||||
struct ScreenInfo {
|
||||
GLuint display_texture;
|
||||
Common::Rectangle<float> display_texcoords;
|
||||
TextureInfo texture;
|
||||
};
|
||||
|
||||
struct PresentationTexture {
|
||||
u32 width = 0;
|
||||
u32 height = 0;
|
||||
OGLTexture texture;
|
||||
};
|
||||
|
||||
class RendererOpenGL : public RendererBase {
|
||||
public:
|
||||
explicit RendererOpenGL(Frontend::EmuWindow& window);
|
||||
~RendererOpenGL() override;
|
||||
|
||||
/// Initialize the renderer
|
||||
VideoCore::ResultStatus Init() override;
|
||||
|
||||
/// Shutdown the renderer
|
||||
void ShutDown() override;
|
||||
|
||||
/// Finalizes rendering the guest frame
|
||||
void SwapBuffers() override;
|
||||
|
||||
/// Draws the latest frame from texture mailbox to the currently bound draw framebuffer in this
|
||||
/// context
|
||||
void TryPresent(int timeout_ms) override;
|
||||
|
||||
/// Prepares for video dumping (e.g. create necessary buffers, etc)
|
||||
void PrepareVideoDumping() override;
|
||||
|
||||
/// Cleans up after video dumping is ended
|
||||
void CleanupVideoDumping() override;
|
||||
|
||||
private:
|
||||
void InitOpenGLObjects();
|
||||
void ReloadSampler();
|
||||
void ReloadShader();
|
||||
void PrepareRendertarget();
|
||||
void RenderScreenshot();
|
||||
void RenderToMailbox(const Layout::FramebufferLayout& layout,
|
||||
std::unique_ptr<Frontend::TextureMailbox>& mailbox, bool flipped);
|
||||
void ConfigureFramebufferTexture(TextureInfo& texture,
|
||||
const GPU::Regs::FramebufferConfig& framebuffer);
|
||||
void DrawScreens(const Layout::FramebufferLayout& layout, bool flipped);
|
||||
void DrawSingleScreenRotated(const ScreenInfo& screen_info, float x, float y, float w, float h);
|
||||
void DrawSingleScreen(const ScreenInfo& screen_info, float x, float y, float w, float h);
|
||||
void DrawSingleScreenStereoRotated(const ScreenInfo& screen_info_l,
|
||||
const ScreenInfo& screen_info_r, float x, float y, float w,
|
||||
float h);
|
||||
void DrawSingleScreenStereo(const ScreenInfo& screen_info_l, const ScreenInfo& screen_info_r,
|
||||
float x, float y, float w, float h);
|
||||
void UpdateFramerate();
|
||||
|
||||
// Loads framebuffer from emulated memory into the display information structure
|
||||
void LoadFBToScreenInfo(const GPU::Regs::FramebufferConfig& framebuffer,
|
||||
ScreenInfo& screen_info, bool right_eye);
|
||||
// Fills active OpenGL texture with the given RGB color.
|
||||
void LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b, const TextureInfo& texture);
|
||||
|
||||
OpenGLState state;
|
||||
|
||||
// OpenGL object IDs
|
||||
OGLVertexArray vertex_array;
|
||||
OGLBuffer vertex_buffer;
|
||||
OGLProgram shader;
|
||||
OGLFramebuffer screenshot_framebuffer;
|
||||
OGLSampler filter_sampler;
|
||||
|
||||
/// Display information for top and bottom screens respectively
|
||||
std::array<ScreenInfo, 3> screen_infos;
|
||||
|
||||
// Shader uniform location indices
|
||||
GLuint uniform_modelview_matrix;
|
||||
GLuint uniform_color_texture;
|
||||
GLuint uniform_color_texture_r;
|
||||
|
||||
// Shader uniform for Dolphin compatibility
|
||||
GLuint uniform_i_resolution;
|
||||
GLuint uniform_o_resolution;
|
||||
GLuint uniform_layer;
|
||||
|
||||
// Shader attribute input indices
|
||||
GLuint attrib_position;
|
||||
GLuint attrib_tex_coord;
|
||||
|
||||
FrameDumperOpenGL frame_dumper;
|
||||
};
|
||||
|
||||
} // namespace OpenGL
|
113
src/video_core/renderer_vulkan/vk_buffer.cpp
Normal file
113
src/video_core/renderer_vulkan/vk_buffer.cpp
Normal file
@ -0,0 +1,113 @@
|
||||
#include "vk_buffer.h"
|
||||
#include "vk_context.h"
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
#include <type_traits>
|
||||
#include <cstring>
|
||||
|
||||
Buffer::Buffer(std::shared_ptr<VkContext> context) :
|
||||
context(context)
|
||||
{
|
||||
}
|
||||
|
||||
Buffer::~Buffer()
|
||||
{
|
||||
auto& device = context->device;
|
||||
if (memory != nullptr)
|
||||
device->unmapMemory(buffer_memory.get());
|
||||
}
|
||||
|
||||
void Buffer::create(uint32_t byte_count, vk::MemoryPropertyFlags properties, vk::BufferUsageFlags usage)
|
||||
{
|
||||
auto& device = context->device;
|
||||
size = byte_count;
|
||||
|
||||
vk::BufferCreateInfo bufferInfo({}, byte_count, usage);
|
||||
buffer = device->createBufferUnique(bufferInfo);
|
||||
|
||||
auto mem_requirements = device->getBufferMemoryRequirements(buffer.get());
|
||||
|
||||
auto memory_type_index = find_memory_type(mem_requirements.memoryTypeBits, properties, context);
|
||||
vk::MemoryAllocateInfo alloc_info(mem_requirements.size, memory_type_index);
|
||||
|
||||
buffer_memory = device->allocateMemoryUnique(alloc_info);
|
||||
device->bindBufferMemory(buffer.get(), buffer_memory.get(), 0);
|
||||
|
||||
// Optionally map the buffer to CPU memory
|
||||
if (properties & vk::MemoryPropertyFlagBits::eHostVisible)
|
||||
memory = device->mapMemory(buffer_memory.get(), 0, byte_count);
|
||||
|
||||
// Create buffer view for texel buffers
|
||||
if (usage & vk::BufferUsageFlagBits::eStorageTexelBuffer || usage & vk::BufferUsageFlagBits::eUniformTexelBuffer)
|
||||
{
|
||||
vk::BufferViewCreateInfo view_info({}, buffer.get(), vk::Format::eR32Uint, 0, byte_count);
|
||||
buffer_view = device->createBufferViewUnique(view_info);
|
||||
}
|
||||
}
|
||||
|
||||
void Buffer::bind(vk::CommandBuffer& command_buffer)
|
||||
{
|
||||
vk::DeviceSize offsets[1] = { 0 };
|
||||
command_buffer.bindVertexBuffers(0, 1, &buffer.get(), offsets);
|
||||
}
|
||||
|
||||
void Buffer::copy_buffer(Buffer& src_buffer, Buffer& dst_buffer, const vk::BufferCopy& region)
|
||||
{
|
||||
auto& context = src_buffer.context;
|
||||
auto& device = context->device;
|
||||
auto& queue = context->graphics_queue;
|
||||
|
||||
vk::CommandBufferAllocateInfo alloc_info(context->command_pool.get(), vk::CommandBufferLevel::ePrimary, 1);
|
||||
vk::CommandBuffer command_buffer = device->allocateCommandBuffers(alloc_info)[0];
|
||||
|
||||
command_buffer.begin({vk::CommandBufferUsageFlagBits::eOneTimeSubmit});
|
||||
command_buffer.copyBuffer(src_buffer.buffer.get(), dst_buffer.buffer.get(), region);
|
||||
command_buffer.end();
|
||||
|
||||
vk::SubmitInfo submit_info({}, {}, {}, 1, &command_buffer);
|
||||
queue.submit(submit_info, nullptr);
|
||||
queue.waitIdle();
|
||||
|
||||
device->freeCommandBuffers(context->command_pool.get(), command_buffer);
|
||||
}
|
||||
|
||||
uint32_t Buffer::find_memory_type(uint32_t type_filter, vk::MemoryPropertyFlags properties, std::shared_ptr<VkContext> context)
|
||||
{
|
||||
vk::PhysicalDeviceMemoryProperties mem_properties = context->physical_device.getMemoryProperties();
|
||||
|
||||
for (uint32_t i = 0; i < mem_properties.memoryTypeCount; i++)
|
||||
{
|
||||
auto flags = mem_properties.memoryTypes[i].propertyFlags;
|
||||
if ((type_filter & (1 << i)) && (flags & properties) == properties)
|
||||
return i;
|
||||
}
|
||||
|
||||
throw std::runtime_error("[VK] Failed to find suitable memory type!");
|
||||
}
|
||||
|
||||
VertexBuffer::VertexBuffer(const std::shared_ptr<VkContext>& context) :
|
||||
host(context), local(context), context(context)
|
||||
{
|
||||
}
|
||||
|
||||
void VertexBuffer::create(uint32_t vertex_count)
|
||||
{
|
||||
// Create a host and local buffer
|
||||
auto byte_count = sizeof(Vertex) * vertex_count;
|
||||
local.create(byte_count, vk::MemoryPropertyFlagBits::eDeviceLocal,
|
||||
vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer);
|
||||
host.create(byte_count, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent,
|
||||
vk::BufferUsageFlagBits::eTransferSrc);
|
||||
}
|
||||
|
||||
void VertexBuffer::copy_vertices(Vertex* vertices, uint32_t count)
|
||||
{
|
||||
auto byte_count = count * sizeof(Vertex);
|
||||
std::memcpy(host.memory, vertices, byte_count);
|
||||
Buffer::copy_buffer(host, local, { 0, 0, byte_count });
|
||||
}
|
||||
|
||||
void VertexBuffer::bind(vk::CommandBuffer& command_buffer)
|
||||
{
|
||||
local.bind(command_buffer);
|
||||
}
|
71
src/video_core/renderer_vulkan/vk_buffer.h
Normal file
71
src/video_core/renderer_vulkan/vk_buffer.h
Normal file
@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
#include <vulkan/vulkan.hpp>
|
||||
#include <glm/glm.hpp>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
|
||||
class VkContext;
|
||||
|
||||
struct VertexInfo
|
||||
{
|
||||
VertexInfo() = default;
|
||||
VertexInfo(glm::vec3 position, glm::vec3 color, glm::vec2 coords) :
|
||||
position(position), color(color), texcoords(coords) {};
|
||||
|
||||
glm::vec3 position;
|
||||
glm::vec3 color;
|
||||
glm::vec2 texcoords;
|
||||
};
|
||||
|
||||
struct Vertex : public VertexInfo
|
||||
{
|
||||
Vertex() = default;
|
||||
Vertex(glm::vec3 position, glm::vec3 color = {}, glm::vec2 coords = {}) : VertexInfo(position, color, coords) {};
|
||||
static constexpr auto binding_desc = vk::VertexInputBindingDescription(0, sizeof(VertexInfo));
|
||||
static constexpr std::array<vk::VertexInputAttributeDescription, 3> attribute_desc =
|
||||
{
|
||||
vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32B32Sfloat, offsetof(VertexInfo, position)),
|
||||
vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32B32Sfloat, offsetof(VertexInfo, color)),
|
||||
vk::VertexInputAttributeDescription(2, 0, vk::Format::eR32G32Sfloat, offsetof(VertexInfo, texcoords)),
|
||||
};
|
||||
};
|
||||
|
||||
class Buffer : public NonCopyable, public Resource
|
||||
{
|
||||
friend class VertexBuffer;
|
||||
public:
|
||||
Buffer(std::shared_ptr<VkContext> context);
|
||||
~Buffer();
|
||||
|
||||
void create(uint32_t size, vk::MemoryPropertyFlags properties, vk::BufferUsageFlags usage);
|
||||
void bind(vk::CommandBuffer& command_buffer);
|
||||
|
||||
static uint32_t find_memory_type(uint32_t type_filter, vk::MemoryPropertyFlags properties, std::shared_ptr<VkContext> context);
|
||||
static void copy_buffer(Buffer& src_buffer, Buffer& dst_buffer, const vk::BufferCopy& region);
|
||||
|
||||
public:
|
||||
void* memory = nullptr;
|
||||
vk::UniqueBuffer buffer;
|
||||
vk::UniqueDeviceMemory buffer_memory;
|
||||
vk::UniqueBufferView buffer_view;
|
||||
uint32_t size = 0;
|
||||
|
||||
protected:
|
||||
std::shared_ptr<VkContext> context;
|
||||
};
|
||||
|
||||
class VertexBuffer
|
||||
{
|
||||
public:
|
||||
VertexBuffer(const std::shared_ptr<VkContext>& context);
|
||||
~VertexBuffer() = default;
|
||||
|
||||
void create(uint32_t vertex_count);
|
||||
void copy_vertices(Vertex* vertices, uint32_t count);
|
||||
void bind(vk::CommandBuffer& command_buffer);
|
||||
|
||||
private:
|
||||
Buffer host, local;
|
||||
std::shared_ptr<VkContext> context;
|
||||
};
|
304
src/video_core/renderer_vulkan/vk_context.cpp
Normal file
304
src/video_core/renderer_vulkan/vk_context.cpp
Normal file
@ -0,0 +1,304 @@
|
||||
#include "vk_context.h"
|
||||
#include "vk_buffer.h"
|
||||
#include "vk_swapchain.h"
|
||||
#include "vk_texture.h"
|
||||
#include <fstream>
|
||||
#include <array>
|
||||
|
||||
PipelineLayoutInfo::PipelineLayoutInfo(const std::shared_ptr<VkContext>& context) :
|
||||
context(context)
|
||||
{
|
||||
}
|
||||
|
||||
PipelineLayoutInfo::~PipelineLayoutInfo()
|
||||
{
|
||||
for (int i = 0; i < shader_stages.size(); i++)
|
||||
context->device->destroyShaderModule(shader_stages[i].module);
|
||||
}
|
||||
|
||||
void PipelineLayoutInfo::add_shader_module(std::string_view filepath, vk::ShaderStageFlagBits stage)
|
||||
{
|
||||
std::ifstream shaderfile(filepath.data(), std::ios::ate | std::ios::binary);
|
||||
|
||||
if (!shaderfile.is_open())
|
||||
throw std::runtime_error("[UTIL] Failed to open shader file!");
|
||||
|
||||
size_t size = shaderfile.tellg();
|
||||
std::vector<char> buffer(size);
|
||||
|
||||
shaderfile.seekg(0);
|
||||
shaderfile.read(buffer.data(), size);
|
||||
shaderfile.close();
|
||||
|
||||
auto module = context->device->createShaderModule({ {}, buffer.size(), reinterpret_cast<const uint32_t*>(buffer.data()) });
|
||||
shader_stages.emplace_back(vk::PipelineShaderStageCreateFlags(), stage, module, "main");
|
||||
}
|
||||
|
||||
void PipelineLayoutInfo::add_resource(Resource* resource, vk::DescriptorType type, vk::ShaderStageFlags stages, int binding, int group)
|
||||
{
|
||||
resource_types[group].first[binding] = resource;
|
||||
resource_types[group].second.emplace_back(binding, type, 1, stages);
|
||||
needed[type]++;
|
||||
}
|
||||
|
||||
VkContext::VkContext(vk::UniqueInstance&& instance_, VkWindow* window) :
|
||||
instance(std::move(instance_)), window(window)
|
||||
{
|
||||
create_devices();
|
||||
}
|
||||
|
||||
VkContext::~VkContext()
|
||||
{
|
||||
device->waitIdle();
|
||||
|
||||
for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++)
|
||||
for (int j = 0; j < descriptor_sets.size(); j++)
|
||||
device->destroyDescriptorSetLayout(descriptor_layouts[i][j]);
|
||||
}
|
||||
|
||||
void VkContext::create(SwapchainInfo& info)
|
||||
{
|
||||
swapchain_info = info;
|
||||
|
||||
// Initialize context
|
||||
create_renderpass();
|
||||
create_command_buffers();
|
||||
}
|
||||
|
||||
vk::CommandBuffer& VkContext::get_command_buffer()
|
||||
{
|
||||
return command_buffers[window->image_index].get();
|
||||
}
|
||||
|
||||
void VkContext::create_devices(int device_id)
|
||||
{
|
||||
// Pick a physical device
|
||||
auto physical_devices = instance->enumeratePhysicalDevices();
|
||||
physical_device = physical_devices.front();
|
||||
|
||||
// Get available queue family properties
|
||||
auto family_props = physical_device.getQueueFamilyProperties();
|
||||
|
||||
// Discover a queue with both graphics and compute capabilities
|
||||
vk::QueueFlags search = vk::QueueFlagBits::eGraphics | vk::QueueFlagBits::eCompute;
|
||||
for (size_t i = 0; i < family_props.size(); i++)
|
||||
{
|
||||
auto& family = family_props[i];
|
||||
if ((family.queueFlags & search) == search)
|
||||
queue_family = i;
|
||||
}
|
||||
|
||||
if (queue_family == -1)
|
||||
throw std::runtime_error("[VK] Could not find appropriate queue families!\n");
|
||||
|
||||
const float default_queue_priority = 0.0f;
|
||||
std::array<const char*, 1> device_extensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME };
|
||||
|
||||
auto queue_info = vk::DeviceQueueCreateInfo({}, queue_family, 1, &default_queue_priority);
|
||||
|
||||
std::array<vk::PhysicalDeviceFeatures, 1> features = {};
|
||||
features[0].samplerAnisotropy = true;
|
||||
|
||||
vk::DeviceCreateInfo device_info({}, 1, &queue_info, 0, nullptr, device_extensions.size(), device_extensions.data(), features.data());
|
||||
device = physical_device.createDeviceUnique(device_info);
|
||||
|
||||
graphics_queue = device->getQueue(queue_family, 0);
|
||||
}
|
||||
|
||||
void VkContext::create_renderpass()
|
||||
{
|
||||
// Color attachment
|
||||
vk::AttachmentReference color_attachment_ref(0, vk::ImageLayout::eColorAttachmentOptimal);
|
||||
vk::AttachmentReference depth_attachment_ref(1, vk::ImageLayout::eDepthStencilAttachmentOptimal);
|
||||
vk::AttachmentDescription attachments[2] =
|
||||
{
|
||||
{
|
||||
{},
|
||||
window->swapchain_info.surface_format.format,
|
||||
vk::SampleCountFlagBits::e1,
|
||||
vk::AttachmentLoadOp::eClear,
|
||||
vk::AttachmentStoreOp::eStore,
|
||||
vk::AttachmentLoadOp::eDontCare,
|
||||
vk::AttachmentStoreOp::eDontCare,
|
||||
vk::ImageLayout::eUndefined,
|
||||
vk::ImageLayout::ePresentSrcKHR
|
||||
},
|
||||
{
|
||||
{},
|
||||
window->swapchain_info.depth_format,
|
||||
vk::SampleCountFlagBits::e1,
|
||||
vk::AttachmentLoadOp::eClear,
|
||||
vk::AttachmentStoreOp::eDontCare,
|
||||
vk::AttachmentLoadOp::eDontCare,
|
||||
vk::AttachmentStoreOp::eDontCare,
|
||||
vk::ImageLayout::eUndefined,
|
||||
vk::ImageLayout::eDepthStencilAttachmentOptimal
|
||||
}
|
||||
};
|
||||
|
||||
vk::SubpassDependency dependency
|
||||
(
|
||||
VK_SUBPASS_EXTERNAL,
|
||||
0,
|
||||
vk::PipelineStageFlagBits::eColorAttachmentOutput | vk::PipelineStageFlagBits::eEarlyFragmentTests,
|
||||
vk::PipelineStageFlagBits::eColorAttachmentOutput | vk::PipelineStageFlagBits::eEarlyFragmentTests,
|
||||
vk::AccessFlagBits::eNone,
|
||||
vk::AccessFlagBits::eColorAttachmentWrite | vk::AccessFlagBits::eDepthStencilAttachmentWrite,
|
||||
vk::DependencyFlagBits::eByRegion
|
||||
);
|
||||
|
||||
vk::SubpassDescription subpass({}, vk::PipelineBindPoint::eGraphics, {}, {}, 1, &color_attachment_ref, {}, &depth_attachment_ref);
|
||||
vk::RenderPassCreateInfo renderpass_info({}, 2, attachments, 1, &subpass, 1, &dependency);
|
||||
renderpass = device->createRenderPassUnique(renderpass_info);
|
||||
}
|
||||
|
||||
void VkContext::create_decriptor_sets(PipelineLayoutInfo &info)
|
||||
{
|
||||
std::vector<vk::DescriptorPoolSize> pool_sizes;
|
||||
pool_sizes.reserve(info.needed.size());
|
||||
for (const auto& [type, count] : info.needed)
|
||||
{
|
||||
pool_sizes.emplace_back(type, count * MAX_FRAMES_IN_FLIGHT);
|
||||
}
|
||||
|
||||
for (const auto& [group, resource_info] : info.resource_types)
|
||||
{
|
||||
auto& bindings = resource_info.second;
|
||||
vk::DescriptorSetLayoutCreateInfo layout_info({}, bindings.size(), bindings.data());
|
||||
|
||||
for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++)
|
||||
descriptor_layouts[i].push_back(device->createDescriptorSetLayout(layout_info));
|
||||
}
|
||||
|
||||
vk::DescriptorPoolCreateInfo pool_info({}, MAX_FRAMES_IN_FLIGHT * descriptor_layouts[0].size(), pool_sizes.size(), pool_sizes.data());
|
||||
descriptor_pool = device->createDescriptorPoolUnique(pool_info);
|
||||
|
||||
for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++)
|
||||
{
|
||||
vk::DescriptorSetAllocateInfo alloc_info(descriptor_pool.get(), descriptor_layouts[i]);
|
||||
descriptor_sets[i] = device->allocateDescriptorSets(alloc_info);
|
||||
|
||||
for (const auto& [group, resource_info] : info.resource_types)
|
||||
{
|
||||
auto& bindings = resource_info.second;
|
||||
std::array<vk::DescriptorImageInfo, MAX_BINDING_COUNT> image_infos;
|
||||
std::array<vk::DescriptorBufferInfo, MAX_BINDING_COUNT> buffer_infos;
|
||||
|
||||
std::vector<vk::WriteDescriptorSet> descriptor_writes;
|
||||
descriptor_writes.reserve(bindings.size());
|
||||
|
||||
auto& set = descriptor_sets[i][group];
|
||||
for (int j = 0; j < bindings.size(); j++)
|
||||
{
|
||||
switch (bindings[j].descriptorType)
|
||||
{
|
||||
case vk::DescriptorType::eCombinedImageSampler:
|
||||
{
|
||||
VkTexture* texture = reinterpret_cast<VkTexture*>(resource_info.first[j]);
|
||||
image_infos[j] = vk::DescriptorImageInfo(texture->texture_sampler.get(), texture->texture_view.get(),
|
||||
vk::ImageLayout::eShaderReadOnlyOptimal);
|
||||
descriptor_writes.emplace_back(set, j, 0, 1, vk::DescriptorType::eCombinedImageSampler, &image_infos[j]);
|
||||
break;
|
||||
}
|
||||
case vk::DescriptorType::eUniformTexelBuffer:
|
||||
case vk::DescriptorType::eStorageTexelBuffer:
|
||||
{
|
||||
Buffer* buffer = reinterpret_cast<Buffer*>(resource_info.first[j]);
|
||||
descriptor_writes.emplace_back(set, j, 0, 1, bindings[j].descriptorType, nullptr, nullptr, &buffer->buffer_view.get());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw std::runtime_error("[VK] Unknown resource");
|
||||
}
|
||||
}
|
||||
|
||||
device->updateDescriptorSets(descriptor_writes, {});
|
||||
descriptor_writes.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VkContext::create_graphics_pipeline(PipelineLayoutInfo& info)
|
||||
{
|
||||
create_decriptor_sets(info);
|
||||
|
||||
vk::PipelineVertexInputStateCreateInfo vertex_input_info
|
||||
(
|
||||
{},
|
||||
1,
|
||||
&Vertex::binding_desc,
|
||||
Vertex::attribute_desc.size(),
|
||||
Vertex::attribute_desc.data()
|
||||
);
|
||||
|
||||
vk::PipelineInputAssemblyStateCreateInfo input_assembly({}, vk::PrimitiveTopology::eTriangleList, VK_FALSE);
|
||||
vk::Viewport viewport(0, 0, window->swapchain_info.extent.width, window->swapchain_info.extent.height, 0, 1);
|
||||
vk::Rect2D scissor({ 0, 0 }, window->swapchain_info.extent);
|
||||
|
||||
vk::PipelineViewportStateCreateInfo viewport_state({}, 1, &viewport, 1, &scissor);
|
||||
vk::PipelineRasterizationStateCreateInfo rasterizer
|
||||
(
|
||||
{},
|
||||
VK_FALSE,
|
||||
VK_FALSE,
|
||||
vk::PolygonMode::eFill,
|
||||
vk::CullModeFlagBits::eNone,
|
||||
vk::FrontFace::eClockwise,
|
||||
VK_FALSE
|
||||
);
|
||||
|
||||
vk::PipelineMultisampleStateCreateInfo multisampling({}, vk::SampleCountFlagBits::e1, VK_FALSE);
|
||||
vk::PipelineColorBlendAttachmentState colorblend_attachment(VK_FALSE);
|
||||
colorblend_attachment.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG |
|
||||
vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA;
|
||||
|
||||
vk::PipelineColorBlendStateCreateInfo color_blending({}, VK_FALSE, vk::LogicOp::eCopy, 1, &colorblend_attachment, {0});
|
||||
|
||||
vk::PipelineLayoutCreateInfo pipeline_layout_info({}, descriptor_layouts[0], {});
|
||||
pipeline_layout = device->createPipelineLayoutUnique(pipeline_layout_info);
|
||||
|
||||
vk::DynamicState dynamic_states[2] = { vk::DynamicState::eDepthCompareOp, vk::DynamicState::eLineWidth };
|
||||
vk::PipelineDynamicStateCreateInfo dynamic_info({}, 2, dynamic_states);
|
||||
|
||||
// Depth and stencil state containing depth and stencil compare and test operations
|
||||
// We only use depth tests and want depth tests and writes to be enabled and compare with less or equal
|
||||
vk::PipelineDepthStencilStateCreateInfo depth_info({}, VK_TRUE, VK_TRUE, vk::CompareOp::eGreaterOrEqual, VK_FALSE, VK_TRUE);
|
||||
depth_info.back.failOp = vk::StencilOp::eKeep;
|
||||
depth_info.back.passOp = vk::StencilOp::eKeep;
|
||||
depth_info.back.compareOp = vk::CompareOp::eAlways;
|
||||
depth_info.front = depth_info.back;
|
||||
|
||||
vk::GraphicsPipelineCreateInfo pipeline_info
|
||||
(
|
||||
{},
|
||||
info.shader_stages.size(),
|
||||
info.shader_stages.data(),
|
||||
&vertex_input_info,
|
||||
&input_assembly,
|
||||
nullptr,
|
||||
&viewport_state,&rasterizer,
|
||||
&multisampling,
|
||||
&depth_info,
|
||||
&color_blending,
|
||||
&dynamic_info,
|
||||
pipeline_layout.get(),
|
||||
renderpass.get()
|
||||
);
|
||||
|
||||
auto pipeline = device->createGraphicsPipelineUnique(nullptr, pipeline_info);
|
||||
if (pipeline.result == vk::Result::eSuccess)
|
||||
graphics_pipeline = std::move(pipeline.value);
|
||||
else
|
||||
throw std::runtime_error("[VK] Couldn't create graphics pipeline");
|
||||
}
|
||||
|
||||
void VkContext::create_command_buffers()
|
||||
{
|
||||
vk::CommandPoolCreateInfo pool_info(vk::CommandPoolCreateFlagBits::eResetCommandBuffer, queue_family);
|
||||
command_pool = device->createCommandPoolUnique(pool_info);
|
||||
|
||||
command_buffers.resize(window->swapchain_info.image_count);
|
||||
|
||||
vk::CommandBufferAllocateInfo alloc_info(command_pool.get(), vk::CommandBufferLevel::ePrimary, command_buffers.size());
|
||||
command_buffers = device->allocateCommandBuffersUnique(alloc_info);
|
||||
}
|
75
src/video_core/renderer_vulkan/vk_context.h
Normal file
75
src/video_core/renderer_vulkan/vk_context.h
Normal file
@ -0,0 +1,75 @@
|
||||
#pragma once
|
||||
#include "vk_swapchain.h"
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
|
||||
class VkWindow;
|
||||
class VkContext;
|
||||
|
||||
constexpr int MAX_BINDING_COUNT = 10;
|
||||
|
||||
struct PipelineLayoutInfo
|
||||
{
|
||||
friend class VkContext;
|
||||
PipelineLayoutInfo(const std::shared_ptr<VkContext>& context);
|
||||
~PipelineLayoutInfo();
|
||||
|
||||
void add_shader_module(std::string_view filepath, vk::ShaderStageFlagBits stage);
|
||||
void add_resource(Resource* resource, vk::DescriptorType type, vk::ShaderStageFlags stages, int binding, int group = 0);
|
||||
|
||||
private:
|
||||
using DescInfo = std::pair<std::array<Resource*, MAX_BINDING_COUNT>, std::vector<vk::DescriptorSetLayoutBinding>>;
|
||||
|
||||
std::shared_ptr<VkContext> context;
|
||||
std::unordered_map<int, DescInfo> resource_types;
|
||||
std::unordered_map<vk::DescriptorType, int> needed;
|
||||
std::vector<vk::PipelineShaderStageCreateInfo> shader_stages;
|
||||
};
|
||||
|
||||
class VkTexture;
|
||||
|
||||
// The vulkan context. Can only be created by the window
|
||||
class VkContext
|
||||
{
|
||||
friend class VkWindow;
|
||||
public:
|
||||
VkContext(vk::UniqueInstance&& instance, VkWindow* window);
|
||||
~VkContext();
|
||||
|
||||
void create(SwapchainInfo& info);
|
||||
void create_graphics_pipeline(PipelineLayoutInfo& info);
|
||||
|
||||
vk::CommandBuffer& get_command_buffer();
|
||||
|
||||
private:
|
||||
void create_devices(int device_id = 0);
|
||||
void create_renderpass();
|
||||
void create_command_buffers();
|
||||
void create_decriptor_sets(PipelineLayoutInfo& info);
|
||||
|
||||
public:
|
||||
// Queue family indexes
|
||||
uint32_t queue_family = -1;
|
||||
|
||||
// Core vulkan objects
|
||||
vk::UniqueInstance instance;
|
||||
vk::PhysicalDevice physical_device;
|
||||
vk::UniqueDevice device;
|
||||
vk::Queue graphics_queue;
|
||||
|
||||
// Pipeline
|
||||
vk::UniquePipelineLayout pipeline_layout;
|
||||
vk::UniquePipeline graphics_pipeline;
|
||||
vk::UniqueRenderPass renderpass;
|
||||
vk::UniqueDescriptorPool descriptor_pool;
|
||||
std::array<std::vector<vk::DescriptorSetLayout>, MAX_FRAMES_IN_FLIGHT> descriptor_layouts;
|
||||
std::array<std::vector<vk::DescriptorSet>, MAX_FRAMES_IN_FLIGHT> descriptor_sets;
|
||||
|
||||
// Command buffer
|
||||
vk::UniqueCommandPool command_pool;
|
||||
std::vector<vk::UniqueCommandBuffer> command_buffers;
|
||||
|
||||
// Window
|
||||
VkWindow* window;
|
||||
SwapchainInfo swapchain_info;
|
||||
};
|
246
src/video_core/renderer_vulkan/vk_resource_manager.cpp
Normal file
246
src/video_core/renderer_vulkan/vk_resource_manager.cpp
Normal file
@ -0,0 +1,246 @@
|
||||
// Copyright 2015 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <utility>
|
||||
#include <glad/glad.h>
|
||||
#include "common/common_types.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "video_core/renderer_opengl/gl_resource_manager.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_util.h"
|
||||
#include "video_core/renderer_opengl/gl_state.h"
|
||||
#include "video_core/renderer_opengl/gl_vars.h"
|
||||
|
||||
MICROPROFILE_DEFINE(OpenGL_ResourceCreation, "OpenGL", "Resource Creation", MP_RGB(128, 128, 192));
|
||||
MICROPROFILE_DEFINE(OpenGL_ResourceDeletion, "OpenGL", "Resource Deletion", MP_RGB(128, 128, 192));
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
void OGLRenderbuffer::Create() {
|
||||
if (handle != 0)
|
||||
return;
|
||||
|
||||
MICROPROFILE_SCOPE(OpenGL_ResourceCreation);
|
||||
glGenRenderbuffers(1, &handle);
|
||||
}
|
||||
|
||||
void OGLRenderbuffer::Release() {
|
||||
if (handle == 0)
|
||||
return;
|
||||
|
||||
MICROPROFILE_SCOPE(OpenGL_ResourceDeletion);
|
||||
glDeleteRenderbuffers(1, &handle);
|
||||
OpenGLState::GetCurState().ResetRenderbuffer(handle).Apply();
|
||||
handle = 0;
|
||||
}
|
||||
|
||||
void OGLTexture::Create() {
|
||||
if (handle != 0)
|
||||
return;
|
||||
|
||||
MICROPROFILE_SCOPE(OpenGL_ResourceCreation);
|
||||
glGenTextures(1, &handle);
|
||||
}
|
||||
|
||||
void OGLTexture::Release() {
|
||||
if (handle == 0)
|
||||
return;
|
||||
|
||||
MICROPROFILE_SCOPE(OpenGL_ResourceDeletion);
|
||||
glDeleteTextures(1, &handle);
|
||||
OpenGLState::GetCurState().ResetTexture(handle).Apply();
|
||||
handle = 0;
|
||||
}
|
||||
|
||||
void OGLTexture::Allocate(GLenum target, GLsizei levels, GLenum internalformat, GLenum format,
|
||||
GLenum type, GLsizei width, GLsizei height, GLsizei depth) {
|
||||
const bool tex_storage = GLAD_GL_ARB_texture_storage || GLES;
|
||||
|
||||
switch (target) {
|
||||
case GL_TEXTURE_1D:
|
||||
case GL_TEXTURE:
|
||||
if (tex_storage) {
|
||||
glTexStorage1D(target, levels, internalformat, width);
|
||||
} else {
|
||||
for (GLsizei level{0}; level < levels; ++level) {
|
||||
glTexImage1D(target, level, internalformat, width, 0, format, type, nullptr);
|
||||
width >>= 1;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case GL_TEXTURE_2D:
|
||||
case GL_TEXTURE_1D_ARRAY:
|
||||
case GL_TEXTURE_RECTANGLE:
|
||||
case GL_TEXTURE_CUBE_MAP:
|
||||
if (tex_storage) {
|
||||
glTexStorage2D(target, levels, internalformat, width, height);
|
||||
} else {
|
||||
for (GLsizei level{0}; level < levels; ++level) {
|
||||
glTexImage2D(target, level, internalformat, width, height, 0, format, type,
|
||||
nullptr);
|
||||
width >>= 1;
|
||||
if (target != GL_TEXTURE_1D_ARRAY)
|
||||
height >>= 1;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case GL_TEXTURE_3D:
|
||||
case GL_TEXTURE_2D_ARRAY:
|
||||
case GL_TEXTURE_CUBE_MAP_ARRAY:
|
||||
if (tex_storage) {
|
||||
glTexStorage3D(target, levels, internalformat, width, height, depth);
|
||||
} else {
|
||||
for (GLsizei level{0}; level < levels; ++level) {
|
||||
glTexImage3D(target, level, internalformat, width, height, depth, 0, format, type,
|
||||
nullptr);
|
||||
}
|
||||
width >>= 1;
|
||||
height >>= 1;
|
||||
if (target == GL_TEXTURE_3D)
|
||||
depth >>= 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (!tex_storage) {
|
||||
glTexParameteri(target, GL_TEXTURE_MAX_LEVEL, levels - 1);
|
||||
}
|
||||
}
|
||||
|
||||
void OGLSampler::Create() {
|
||||
if (handle != 0)
|
||||
return;
|
||||
|
||||
MICROPROFILE_SCOPE(OpenGL_ResourceCreation);
|
||||
glGenSamplers(1, &handle);
|
||||
}
|
||||
|
||||
void OGLSampler::Release() {
|
||||
if (handle == 0)
|
||||
return;
|
||||
|
||||
MICROPROFILE_SCOPE(OpenGL_ResourceDeletion);
|
||||
glDeleteSamplers(1, &handle);
|
||||
OpenGLState::GetCurState().ResetSampler(handle).Apply();
|
||||
handle = 0;
|
||||
}
|
||||
|
||||
void OGLShader::Create(const char* source, GLenum type) {
|
||||
if (handle != 0)
|
||||
return;
|
||||
if (source == nullptr)
|
||||
return;
|
||||
|
||||
MICROPROFILE_SCOPE(OpenGL_ResourceCreation);
|
||||
handle = LoadShader(source, type);
|
||||
}
|
||||
|
||||
void OGLShader::Release() {
|
||||
if (handle == 0)
|
||||
return;
|
||||
|
||||
MICROPROFILE_SCOPE(OpenGL_ResourceDeletion);
|
||||
glDeleteShader(handle);
|
||||
handle = 0;
|
||||
}
|
||||
|
||||
void OGLProgram::Create(bool separable_program, const std::vector<GLuint>& shaders) {
|
||||
if (handle != 0)
|
||||
return;
|
||||
|
||||
MICROPROFILE_SCOPE(OpenGL_ResourceCreation);
|
||||
handle = LoadProgram(separable_program, shaders);
|
||||
}
|
||||
|
||||
void OGLProgram::Create(const char* vert_shader, const char* frag_shader) {
|
||||
OGLShader vert, frag;
|
||||
vert.Create(vert_shader, GL_VERTEX_SHADER);
|
||||
frag.Create(frag_shader, GL_FRAGMENT_SHADER);
|
||||
|
||||
MICROPROFILE_SCOPE(OpenGL_ResourceCreation);
|
||||
Create(false, {vert.handle, frag.handle});
|
||||
}
|
||||
|
||||
void OGLProgram::Release() {
|
||||
if (handle == 0)
|
||||
return;
|
||||
|
||||
MICROPROFILE_SCOPE(OpenGL_ResourceDeletion);
|
||||
glDeleteProgram(handle);
|
||||
OpenGLState::GetCurState().ResetProgram(handle).Apply();
|
||||
handle = 0;
|
||||
}
|
||||
|
||||
void OGLPipeline::Create() {
|
||||
if (handle != 0)
|
||||
return;
|
||||
|
||||
MICROPROFILE_SCOPE(OpenGL_ResourceCreation);
|
||||
glGenProgramPipelines(1, &handle);
|
||||
}
|
||||
|
||||
void OGLPipeline::Release() {
|
||||
if (handle == 0)
|
||||
return;
|
||||
|
||||
MICROPROFILE_SCOPE(OpenGL_ResourceDeletion);
|
||||
glDeleteProgramPipelines(1, &handle);
|
||||
OpenGLState::GetCurState().ResetPipeline(handle).Apply();
|
||||
handle = 0;
|
||||
}
|
||||
|
||||
void OGLBuffer::Create() {
|
||||
if (handle != 0)
|
||||
return;
|
||||
|
||||
MICROPROFILE_SCOPE(OpenGL_ResourceCreation);
|
||||
glGenBuffers(1, &handle);
|
||||
}
|
||||
|
||||
void OGLBuffer::Release() {
|
||||
if (handle == 0)
|
||||
return;
|
||||
|
||||
MICROPROFILE_SCOPE(OpenGL_ResourceDeletion);
|
||||
glDeleteBuffers(1, &handle);
|
||||
OpenGLState::GetCurState().ResetBuffer(handle).Apply();
|
||||
handle = 0;
|
||||
}
|
||||
|
||||
void OGLVertexArray::Create() {
|
||||
if (handle != 0)
|
||||
return;
|
||||
|
||||
MICROPROFILE_SCOPE(OpenGL_ResourceCreation);
|
||||
glGenVertexArrays(1, &handle);
|
||||
}
|
||||
|
||||
void OGLVertexArray::Release() {
|
||||
if (handle == 0)
|
||||
return;
|
||||
|
||||
MICROPROFILE_SCOPE(OpenGL_ResourceDeletion);
|
||||
glDeleteVertexArrays(1, &handle);
|
||||
OpenGLState::GetCurState().ResetVertexArray(handle).Apply();
|
||||
handle = 0;
|
||||
}
|
||||
|
||||
void OGLFramebuffer::Create() {
|
||||
if (handle != 0)
|
||||
return;
|
||||
|
||||
MICROPROFILE_SCOPE(OpenGL_ResourceCreation);
|
||||
glGenFramebuffers(1, &handle);
|
||||
}
|
||||
|
||||
void OGLFramebuffer::Release() {
|
||||
if (handle == 0)
|
||||
return;
|
||||
|
||||
MICROPROFILE_SCOPE(OpenGL_ResourceDeletion);
|
||||
glDeleteFramebuffers(1, &handle);
|
||||
OpenGLState::GetCurState().ResetFramebuffer(handle).Apply();
|
||||
handle = 0;
|
||||
}
|
||||
|
||||
} // namespace OpenGL
|
243
src/video_core/renderer_vulkan/vk_resource_manager.h
Normal file
243
src/video_core/renderer_vulkan/vk_resource_manager.h
Normal file
@ -0,0 +1,243 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <vulkan/vulkan.hpp>
|
||||
#include <glm/glm.hpp>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
class VKContext;
|
||||
|
||||
struct VertexInfo
|
||||
{
|
||||
VertexInfo() = default;
|
||||
VertexInfo(glm::vec3 position, glm::vec3 color, glm::vec2 coords) :
|
||||
position(position), color(color), texcoords(coords) {};
|
||||
|
||||
glm::vec3 position;
|
||||
glm::vec3 color;
|
||||
glm::vec2 texcoords;
|
||||
};
|
||||
|
||||
struct Vertex : public VertexInfo
|
||||
{
|
||||
Vertex() = default;
|
||||
Vertex(glm::vec3 position, glm::vec3 color = {}, glm::vec2 coords = {}) : VertexInfo(position, color, coords) {};
|
||||
static constexpr auto binding_desc = vk::VertexInputBindingDescription(0, sizeof(VertexInfo));
|
||||
static constexpr std::array<vk::VertexInputAttributeDescription, 3> attribute_desc =
|
||||
{
|
||||
vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32B32Sfloat, offsetof(VertexInfo, position)),
|
||||
vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32B32Sfloat, offsetof(VertexInfo, color)),
|
||||
vk::VertexInputAttributeDescription(2, 0, vk::Format::eR32G32Sfloat, offsetof(VertexInfo, texcoords)),
|
||||
};
|
||||
};
|
||||
|
||||
class VKBuffer : public NonCopyable, public Resource
|
||||
{
|
||||
friend class VertexBuffer;
|
||||
public:
|
||||
VKBuffer(std::shared_ptr<VKContext> context);
|
||||
~VKBuffer();
|
||||
|
||||
void Create(uint32_t size, vk::MemoryPropertyFlags properties, vk::BufferUsageFlags usage);
|
||||
void Bind(vk::CommandBuffer& command_buffer);
|
||||
|
||||
static uint32_t FindMemoryType(uint32_t type_filter, vk::MemoryPropertyFlags properties, std::shared_ptr<VKContext> context);
|
||||
static void CopyBuffer(VKBuffer& src_buffer, VKBuffer& dst_buffer, const vk::BufferCopy& region);
|
||||
|
||||
public:
|
||||
void* memory = nullptr;
|
||||
vk::UniqueBuffer buffer;
|
||||
vk::UniqueDeviceMemory buffer_memory;
|
||||
vk::UniqueBufferView buffer_view;
|
||||
uint32_t size = 0;
|
||||
|
||||
protected:
|
||||
std::shared_ptr<VKContext> context;
|
||||
};
|
||||
|
||||
class VKTexture : public NonCopyable, public Resource
|
||||
{
|
||||
friend class VkContext;
|
||||
public:
|
||||
VKTexture(const std::shared_ptr<VKContext>& context);
|
||||
~VKTexture() = default;
|
||||
|
||||
void Create(int width, int height, vk::ImageType type, vk::Format format = vk::Format::eR8G8B8A8Uint);
|
||||
void CopyPixels(uint8_t* pixels, uint32_t count);
|
||||
|
||||
private:
|
||||
void TransitionLayout(vk::ImageLayout old_layout, vk::ImageLayout new_layout);
|
||||
|
||||
private:
|
||||
// Texture buffer
|
||||
void* pixels = nullptr;
|
||||
std::shared_ptr<VKContext> context;
|
||||
uint32_t width = 0, height = 0, channels = 0;
|
||||
VKBuffer staging;
|
||||
|
||||
// Texture objects
|
||||
vk::UniqueImage texture;
|
||||
vk::UniqueImageView texture_view;
|
||||
vk::UniqueDeviceMemory texture_memory;
|
||||
vk::UniqueSampler texture_sampler;
|
||||
vk::Format format;
|
||||
};
|
||||
|
||||
class OGLShader : private NonCopyable {
|
||||
public:
|
||||
OGLShader() = default;
|
||||
|
||||
OGLShader(OGLShader&& o) noexcept : handle(std::exchange(o.handle, 0)) {}
|
||||
|
||||
~OGLShader() {
|
||||
Release();
|
||||
}
|
||||
|
||||
OGLShader& operator=(OGLShader&& o) noexcept {
|
||||
Release();
|
||||
handle = std::exchange(o.handle, 0);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Create(const char* source, GLenum type);
|
||||
|
||||
void Release();
|
||||
|
||||
GLuint handle = 0;
|
||||
};
|
||||
|
||||
class OGLProgram : private NonCopyable {
|
||||
public:
|
||||
OGLProgram() = default;
|
||||
|
||||
OGLProgram(OGLProgram&& o) noexcept : handle(std::exchange(o.handle, 0)) {}
|
||||
|
||||
~OGLProgram() {
|
||||
Release();
|
||||
}
|
||||
|
||||
OGLProgram& operator=(OGLProgram&& o) noexcept {
|
||||
Release();
|
||||
handle = std::exchange(o.handle, 0);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Creates a new program from given shader objects
|
||||
void Create(bool separable_program, const std::vector<GLuint>& shaders);
|
||||
|
||||
/// Creates a new program from given shader soruce code
|
||||
void Create(const char* vert_shader, const char* frag_shader);
|
||||
|
||||
/// Deletes the internal OpenGL resource
|
||||
void Release();
|
||||
|
||||
GLuint handle = 0;
|
||||
};
|
||||
|
||||
class OGLPipeline : private NonCopyable {
|
||||
public:
|
||||
OGLPipeline() = default;
|
||||
OGLPipeline(OGLPipeline&& o) noexcept {
|
||||
handle = std::exchange<GLuint>(o.handle, 0);
|
||||
}
|
||||
~OGLPipeline() {
|
||||
Release();
|
||||
}
|
||||
OGLPipeline& operator=(OGLPipeline&& o) noexcept {
|
||||
Release();
|
||||
handle = std::exchange<GLuint>(o.handle, 0);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Creates a new internal OpenGL resource and stores the handle
|
||||
void Create();
|
||||
|
||||
/// Deletes the internal OpenGL resource
|
||||
void Release();
|
||||
|
||||
GLuint handle = 0;
|
||||
};
|
||||
|
||||
class OGLBuffer : private NonCopyable {
|
||||
public:
|
||||
OGLBuffer() = default;
|
||||
|
||||
OGLBuffer(OGLBuffer&& o) noexcept : handle(std::exchange(o.handle, 0)) {}
|
||||
|
||||
~OGLBuffer() {
|
||||
Release();
|
||||
}
|
||||
|
||||
OGLBuffer& operator=(OGLBuffer&& o) noexcept {
|
||||
Release();
|
||||
handle = std::exchange(o.handle, 0);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Creates a new internal OpenGL resource and stores the handle
|
||||
void Create();
|
||||
|
||||
/// Deletes the internal OpenGL resource
|
||||
void Release();
|
||||
|
||||
GLuint handle = 0;
|
||||
};
|
||||
|
||||
class OGLVertexArray : private NonCopyable {
|
||||
public:
|
||||
OGLVertexArray() = default;
|
||||
|
||||
OGLVertexArray(OGLVertexArray&& o) noexcept : handle(std::exchange(o.handle, 0)) {}
|
||||
|
||||
~OGLVertexArray() {
|
||||
Release();
|
||||
}
|
||||
|
||||
OGLVertexArray& operator=(OGLVertexArray&& o) noexcept {
|
||||
Release();
|
||||
handle = std::exchange(o.handle, 0);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Creates a new internal OpenGL resource and stores the handle
|
||||
void Create();
|
||||
|
||||
/// Deletes the internal OpenGL resource
|
||||
void Release();
|
||||
|
||||
GLuint handle = 0;
|
||||
};
|
||||
|
||||
class OGLFramebuffer : private NonCopyable {
|
||||
public:
|
||||
OGLFramebuffer() = default;
|
||||
|
||||
OGLFramebuffer(OGLFramebuffer&& o) noexcept : handle(std::exchange(o.handle, 0)) {}
|
||||
|
||||
~OGLFramebuffer() {
|
||||
Release();
|
||||
}
|
||||
|
||||
OGLFramebuffer& operator=(OGLFramebuffer&& o) noexcept {
|
||||
Release();
|
||||
handle = std::exchange(o.handle, 0);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Creates a new internal OpenGL resource and stores the handle
|
||||
void Create();
|
||||
|
||||
/// Deletes the internal OpenGL resource
|
||||
void Release();
|
||||
|
||||
GLuint handle = 0;
|
||||
};
|
||||
|
||||
} // namespace OpenGL
|
452
src/video_core/renderer_vulkan/vk_state.cpp
Normal file
452
src/video_core/renderer_vulkan/vk_state.cpp
Normal file
@ -0,0 +1,452 @@
|
||||
// Copyright 2015 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <glad/glad.h>
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "video_core/renderer_opengl/gl_state.h"
|
||||
#include "video_core/renderer_opengl/gl_vars.h"
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
OpenGLState OpenGLState::cur_state;
|
||||
|
||||
OpenGLState::OpenGLState() {
|
||||
// These all match default OpenGL values
|
||||
cull.enabled = false;
|
||||
cull.mode = GL_BACK;
|
||||
cull.front_face = GL_CCW;
|
||||
|
||||
depth.test_enabled = false;
|
||||
depth.test_func = GL_LESS;
|
||||
depth.write_mask = GL_TRUE;
|
||||
|
||||
color_mask.red_enabled = GL_TRUE;
|
||||
color_mask.green_enabled = GL_TRUE;
|
||||
color_mask.blue_enabled = GL_TRUE;
|
||||
color_mask.alpha_enabled = GL_TRUE;
|
||||
|
||||
stencil.test_enabled = false;
|
||||
stencil.test_func = GL_ALWAYS;
|
||||
stencil.test_ref = 0;
|
||||
stencil.test_mask = 0xFF;
|
||||
stencil.write_mask = 0xFF;
|
||||
stencil.action_depth_fail = GL_KEEP;
|
||||
stencil.action_depth_pass = GL_KEEP;
|
||||
stencil.action_stencil_fail = GL_KEEP;
|
||||
|
||||
blend.enabled = true;
|
||||
blend.rgb_equation = GL_FUNC_ADD;
|
||||
blend.a_equation = GL_FUNC_ADD;
|
||||
blend.src_rgb_func = GL_ONE;
|
||||
blend.dst_rgb_func = GL_ZERO;
|
||||
blend.src_a_func = GL_ONE;
|
||||
blend.dst_a_func = GL_ZERO;
|
||||
blend.color.red = 0.0f;
|
||||
blend.color.green = 0.0f;
|
||||
blend.color.blue = 0.0f;
|
||||
blend.color.alpha = 0.0f;
|
||||
|
||||
logic_op = GL_COPY;
|
||||
|
||||
for (auto& texture_unit : texture_units) {
|
||||
texture_unit.texture_2d = 0;
|
||||
texture_unit.sampler = 0;
|
||||
}
|
||||
|
||||
texture_cube_unit.texture_cube = 0;
|
||||
texture_cube_unit.sampler = 0;
|
||||
|
||||
texture_buffer_lut_lf.texture_buffer = 0;
|
||||
texture_buffer_lut_rg.texture_buffer = 0;
|
||||
texture_buffer_lut_rgba.texture_buffer = 0;
|
||||
|
||||
image_shadow_buffer = 0;
|
||||
image_shadow_texture_px = 0;
|
||||
image_shadow_texture_nx = 0;
|
||||
image_shadow_texture_py = 0;
|
||||
image_shadow_texture_ny = 0;
|
||||
image_shadow_texture_pz = 0;
|
||||
image_shadow_texture_nz = 0;
|
||||
|
||||
draw.read_framebuffer = 0;
|
||||
draw.draw_framebuffer = 0;
|
||||
draw.vertex_array = 0;
|
||||
draw.vertex_buffer = 0;
|
||||
draw.uniform_buffer = 0;
|
||||
draw.shader_program = 0;
|
||||
draw.program_pipeline = 0;
|
||||
|
||||
scissor.enabled = false;
|
||||
scissor.x = 0;
|
||||
scissor.y = 0;
|
||||
scissor.width = 0;
|
||||
scissor.height = 0;
|
||||
|
||||
viewport.x = 0;
|
||||
viewport.y = 0;
|
||||
viewport.width = 0;
|
||||
viewport.height = 0;
|
||||
|
||||
clip_distance = {};
|
||||
|
||||
renderbuffer = 0;
|
||||
}
|
||||
|
||||
void OpenGLState::Apply() const {
|
||||
// Culling
|
||||
if (cull.enabled != cur_state.cull.enabled) {
|
||||
if (cull.enabled) {
|
||||
glEnable(GL_CULL_FACE);
|
||||
} else {
|
||||
glDisable(GL_CULL_FACE);
|
||||
}
|
||||
}
|
||||
|
||||
if (cull.mode != cur_state.cull.mode) {
|
||||
glCullFace(cull.mode);
|
||||
}
|
||||
|
||||
if (cull.front_face != cur_state.cull.front_face) {
|
||||
glFrontFace(cull.front_face);
|
||||
}
|
||||
|
||||
// Depth test
|
||||
if (depth.test_enabled != cur_state.depth.test_enabled) {
|
||||
if (depth.test_enabled) {
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
} else {
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
}
|
||||
}
|
||||
|
||||
if (depth.test_func != cur_state.depth.test_func) {
|
||||
glDepthFunc(depth.test_func);
|
||||
}
|
||||
|
||||
// Depth mask
|
||||
if (depth.write_mask != cur_state.depth.write_mask) {
|
||||
glDepthMask(depth.write_mask);
|
||||
}
|
||||
|
||||
// Color mask
|
||||
if (color_mask.red_enabled != cur_state.color_mask.red_enabled ||
|
||||
color_mask.green_enabled != cur_state.color_mask.green_enabled ||
|
||||
color_mask.blue_enabled != cur_state.color_mask.blue_enabled ||
|
||||
color_mask.alpha_enabled != cur_state.color_mask.alpha_enabled) {
|
||||
glColorMask(color_mask.red_enabled, color_mask.green_enabled, color_mask.blue_enabled,
|
||||
color_mask.alpha_enabled);
|
||||
}
|
||||
|
||||
// Stencil test
|
||||
if (stencil.test_enabled != cur_state.stencil.test_enabled) {
|
||||
if (stencil.test_enabled) {
|
||||
glEnable(GL_STENCIL_TEST);
|
||||
} else {
|
||||
glDisable(GL_STENCIL_TEST);
|
||||
}
|
||||
}
|
||||
|
||||
if (stencil.test_func != cur_state.stencil.test_func ||
|
||||
stencil.test_ref != cur_state.stencil.test_ref ||
|
||||
stencil.test_mask != cur_state.stencil.test_mask) {
|
||||
glStencilFunc(stencil.test_func, stencil.test_ref, stencil.test_mask);
|
||||
}
|
||||
|
||||
if (stencil.action_depth_fail != cur_state.stencil.action_depth_fail ||
|
||||
stencil.action_depth_pass != cur_state.stencil.action_depth_pass ||
|
||||
stencil.action_stencil_fail != cur_state.stencil.action_stencil_fail) {
|
||||
glStencilOp(stencil.action_stencil_fail, stencil.action_depth_fail,
|
||||
stencil.action_depth_pass);
|
||||
}
|
||||
|
||||
// Stencil mask
|
||||
if (stencil.write_mask != cur_state.stencil.write_mask) {
|
||||
glStencilMask(stencil.write_mask);
|
||||
}
|
||||
|
||||
// Blending
|
||||
if (blend.enabled != cur_state.blend.enabled) {
|
||||
if (blend.enabled) {
|
||||
glEnable(GL_BLEND);
|
||||
} else {
|
||||
glDisable(GL_BLEND);
|
||||
}
|
||||
|
||||
// GLES does not support glLogicOp
|
||||
if (!GLES) {
|
||||
if (blend.enabled) {
|
||||
glDisable(GL_COLOR_LOGIC_OP);
|
||||
} else {
|
||||
glEnable(GL_COLOR_LOGIC_OP);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (blend.color.red != cur_state.blend.color.red ||
|
||||
blend.color.green != cur_state.blend.color.green ||
|
||||
blend.color.blue != cur_state.blend.color.blue ||
|
||||
blend.color.alpha != cur_state.blend.color.alpha) {
|
||||
glBlendColor(blend.color.red, blend.color.green, blend.color.blue, blend.color.alpha);
|
||||
}
|
||||
|
||||
if (blend.src_rgb_func != cur_state.blend.src_rgb_func ||
|
||||
blend.dst_rgb_func != cur_state.blend.dst_rgb_func ||
|
||||
blend.src_a_func != cur_state.blend.src_a_func ||
|
||||
blend.dst_a_func != cur_state.blend.dst_a_func) {
|
||||
glBlendFuncSeparate(blend.src_rgb_func, blend.dst_rgb_func, blend.src_a_func,
|
||||
blend.dst_a_func);
|
||||
}
|
||||
|
||||
if (blend.rgb_equation != cur_state.blend.rgb_equation ||
|
||||
blend.a_equation != cur_state.blend.a_equation) {
|
||||
glBlendEquationSeparate(blend.rgb_equation, blend.a_equation);
|
||||
}
|
||||
|
||||
// GLES does not support glLogicOp
|
||||
if (!GLES) {
|
||||
if (logic_op != cur_state.logic_op) {
|
||||
glLogicOp(logic_op);
|
||||
}
|
||||
}
|
||||
|
||||
// Textures
|
||||
for (u32 i = 0; i < texture_units.size(); ++i) {
|
||||
if (texture_units[i].texture_2d != cur_state.texture_units[i].texture_2d) {
|
||||
glActiveTexture(TextureUnits::PicaTexture(i).Enum());
|
||||
glBindTexture(GL_TEXTURE_2D, texture_units[i].texture_2d);
|
||||
}
|
||||
if (texture_units[i].sampler != cur_state.texture_units[i].sampler) {
|
||||
glBindSampler(i, texture_units[i].sampler);
|
||||
}
|
||||
}
|
||||
|
||||
if (texture_cube_unit.texture_cube != cur_state.texture_cube_unit.texture_cube) {
|
||||
glActiveTexture(TextureUnits::TextureCube.Enum());
|
||||
glBindTexture(GL_TEXTURE_CUBE_MAP, texture_cube_unit.texture_cube);
|
||||
}
|
||||
if (texture_cube_unit.sampler != cur_state.texture_cube_unit.sampler) {
|
||||
glBindSampler(TextureUnits::TextureCube.id, texture_cube_unit.sampler);
|
||||
}
|
||||
|
||||
// Texture buffer LUTs
|
||||
if (texture_buffer_lut_lf.texture_buffer != cur_state.texture_buffer_lut_lf.texture_buffer) {
|
||||
glActiveTexture(TextureUnits::TextureBufferLUT_LF.Enum());
|
||||
glBindTexture(GL_TEXTURE_BUFFER, texture_buffer_lut_lf.texture_buffer);
|
||||
}
|
||||
|
||||
// Texture buffer LUTs
|
||||
if (texture_buffer_lut_rg.texture_buffer != cur_state.texture_buffer_lut_rg.texture_buffer) {
|
||||
glActiveTexture(TextureUnits::TextureBufferLUT_RG.Enum());
|
||||
glBindTexture(GL_TEXTURE_BUFFER, texture_buffer_lut_rg.texture_buffer);
|
||||
}
|
||||
|
||||
// Texture buffer LUTs
|
||||
if (texture_buffer_lut_rgba.texture_buffer !=
|
||||
cur_state.texture_buffer_lut_rgba.texture_buffer) {
|
||||
glActiveTexture(TextureUnits::TextureBufferLUT_RGBA.Enum());
|
||||
glBindTexture(GL_TEXTURE_BUFFER, texture_buffer_lut_rgba.texture_buffer);
|
||||
}
|
||||
|
||||
// Shadow Images
|
||||
if (image_shadow_buffer != cur_state.image_shadow_buffer) {
|
||||
glBindImageTexture(ImageUnits::ShadowBuffer, image_shadow_buffer, 0, GL_FALSE, 0,
|
||||
GL_READ_WRITE, GL_R32UI);
|
||||
}
|
||||
|
||||
if (image_shadow_texture_px != cur_state.image_shadow_texture_px) {
|
||||
glBindImageTexture(ImageUnits::ShadowTexturePX, image_shadow_texture_px, 0, GL_FALSE, 0,
|
||||
GL_READ_ONLY, GL_R32UI);
|
||||
}
|
||||
|
||||
if (image_shadow_texture_nx != cur_state.image_shadow_texture_nx) {
|
||||
glBindImageTexture(ImageUnits::ShadowTextureNX, image_shadow_texture_nx, 0, GL_FALSE, 0,
|
||||
GL_READ_ONLY, GL_R32UI);
|
||||
}
|
||||
|
||||
if (image_shadow_texture_py != cur_state.image_shadow_texture_py) {
|
||||
glBindImageTexture(ImageUnits::ShadowTexturePY, image_shadow_texture_py, 0, GL_FALSE, 0,
|
||||
GL_READ_ONLY, GL_R32UI);
|
||||
}
|
||||
|
||||
if (image_shadow_texture_ny != cur_state.image_shadow_texture_ny) {
|
||||
glBindImageTexture(ImageUnits::ShadowTextureNY, image_shadow_texture_ny, 0, GL_FALSE, 0,
|
||||
GL_READ_ONLY, GL_R32UI);
|
||||
}
|
||||
|
||||
if (image_shadow_texture_pz != cur_state.image_shadow_texture_pz) {
|
||||
glBindImageTexture(ImageUnits::ShadowTexturePZ, image_shadow_texture_pz, 0, GL_FALSE, 0,
|
||||
GL_READ_ONLY, GL_R32UI);
|
||||
}
|
||||
|
||||
if (image_shadow_texture_nz != cur_state.image_shadow_texture_nz) {
|
||||
glBindImageTexture(ImageUnits::ShadowTextureNZ, image_shadow_texture_nz, 0, GL_FALSE, 0,
|
||||
GL_READ_ONLY, GL_R32UI);
|
||||
}
|
||||
|
||||
// Framebuffer
|
||||
if (draw.read_framebuffer != cur_state.draw.read_framebuffer) {
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, draw.read_framebuffer);
|
||||
}
|
||||
if (draw.draw_framebuffer != cur_state.draw.draw_framebuffer) {
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, draw.draw_framebuffer);
|
||||
}
|
||||
|
||||
// Vertex array
|
||||
if (draw.vertex_array != cur_state.draw.vertex_array) {
|
||||
glBindVertexArray(draw.vertex_array);
|
||||
}
|
||||
|
||||
// Vertex buffer
|
||||
if (draw.vertex_buffer != cur_state.draw.vertex_buffer) {
|
||||
glBindBuffer(GL_ARRAY_BUFFER, draw.vertex_buffer);
|
||||
}
|
||||
|
||||
// Uniform buffer
|
||||
if (draw.uniform_buffer != cur_state.draw.uniform_buffer) {
|
||||
glBindBuffer(GL_UNIFORM_BUFFER, draw.uniform_buffer);
|
||||
}
|
||||
|
||||
// Shader program
|
||||
if (draw.shader_program != cur_state.draw.shader_program) {
|
||||
glUseProgram(draw.shader_program);
|
||||
}
|
||||
|
||||
// Program pipeline
|
||||
if (draw.program_pipeline != cur_state.draw.program_pipeline) {
|
||||
glBindProgramPipeline(draw.program_pipeline);
|
||||
}
|
||||
|
||||
// Scissor test
|
||||
if (scissor.enabled != cur_state.scissor.enabled) {
|
||||
if (scissor.enabled) {
|
||||
glEnable(GL_SCISSOR_TEST);
|
||||
} else {
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
}
|
||||
}
|
||||
|
||||
if (scissor.x != cur_state.scissor.x || scissor.y != cur_state.scissor.y ||
|
||||
scissor.width != cur_state.scissor.width || scissor.height != cur_state.scissor.height) {
|
||||
glScissor(scissor.x, scissor.y, scissor.width, scissor.height);
|
||||
}
|
||||
|
||||
if (viewport.x != cur_state.viewport.x || viewport.y != cur_state.viewport.y ||
|
||||
viewport.width != cur_state.viewport.width ||
|
||||
viewport.height != cur_state.viewport.height) {
|
||||
glViewport(viewport.x, viewport.y, viewport.width, viewport.height);
|
||||
}
|
||||
|
||||
// Clip distance
|
||||
if (!GLES || GLAD_GL_EXT_clip_cull_distance) {
|
||||
for (size_t i = 0; i < clip_distance.size(); ++i) {
|
||||
if (clip_distance[i] != cur_state.clip_distance[i]) {
|
||||
if (clip_distance[i]) {
|
||||
glEnable(GL_CLIP_DISTANCE0 + static_cast<GLenum>(i));
|
||||
} else {
|
||||
glDisable(GL_CLIP_DISTANCE0 + static_cast<GLenum>(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (renderbuffer != cur_state.renderbuffer) {
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
|
||||
}
|
||||
|
||||
cur_state = *this;
|
||||
}
|
||||
|
||||
OpenGLState& OpenGLState::ResetTexture(GLuint handle) {
|
||||
for (auto& unit : texture_units) {
|
||||
if (unit.texture_2d == handle) {
|
||||
unit.texture_2d = 0;
|
||||
}
|
||||
}
|
||||
if (texture_cube_unit.texture_cube == handle)
|
||||
texture_cube_unit.texture_cube = 0;
|
||||
if (texture_buffer_lut_lf.texture_buffer == handle)
|
||||
texture_buffer_lut_lf.texture_buffer = 0;
|
||||
if (texture_buffer_lut_rg.texture_buffer == handle)
|
||||
texture_buffer_lut_rg.texture_buffer = 0;
|
||||
if (texture_buffer_lut_rgba.texture_buffer == handle)
|
||||
texture_buffer_lut_rgba.texture_buffer = 0;
|
||||
if (image_shadow_buffer == handle)
|
||||
image_shadow_buffer = 0;
|
||||
if (image_shadow_texture_px == handle)
|
||||
image_shadow_texture_px = 0;
|
||||
if (image_shadow_texture_nx == handle)
|
||||
image_shadow_texture_nx = 0;
|
||||
if (image_shadow_texture_py == handle)
|
||||
image_shadow_texture_py = 0;
|
||||
if (image_shadow_texture_ny == handle)
|
||||
image_shadow_texture_ny = 0;
|
||||
if (image_shadow_texture_pz == handle)
|
||||
image_shadow_texture_pz = 0;
|
||||
if (image_shadow_texture_nz == handle)
|
||||
image_shadow_texture_nz = 0;
|
||||
return *this;
|
||||
}
|
||||
|
||||
OpenGLState& OpenGLState::ResetSampler(GLuint handle) {
|
||||
for (auto& unit : texture_units) {
|
||||
if (unit.sampler == handle) {
|
||||
unit.sampler = 0;
|
||||
}
|
||||
}
|
||||
if (texture_cube_unit.sampler == handle) {
|
||||
texture_cube_unit.sampler = 0;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
OpenGLState& OpenGLState::ResetProgram(GLuint handle) {
|
||||
if (draw.shader_program == handle) {
|
||||
draw.shader_program = 0;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
OpenGLState& OpenGLState::ResetPipeline(GLuint handle) {
|
||||
if (draw.program_pipeline == handle) {
|
||||
draw.program_pipeline = 0;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
OpenGLState& OpenGLState::ResetBuffer(GLuint handle) {
|
||||
if (draw.vertex_buffer == handle) {
|
||||
draw.vertex_buffer = 0;
|
||||
}
|
||||
if (draw.uniform_buffer == handle) {
|
||||
draw.uniform_buffer = 0;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
OpenGLState& OpenGLState::ResetVertexArray(GLuint handle) {
|
||||
if (draw.vertex_array == handle) {
|
||||
draw.vertex_array = 0;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
OpenGLState& OpenGLState::ResetFramebuffer(GLuint handle) {
|
||||
if (draw.read_framebuffer == handle) {
|
||||
draw.read_framebuffer = 0;
|
||||
}
|
||||
if (draw.draw_framebuffer == handle) {
|
||||
draw.draw_framebuffer = 0;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
OpenGLState& OpenGLState::ResetRenderbuffer(GLuint handle) {
|
||||
if (renderbuffer == handle) {
|
||||
renderbuffer = 0;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
} // namespace OpenGL
|
150
src/video_core/renderer_vulkan/vk_state.h
Normal file
150
src/video_core/renderer_vulkan/vk_state.h
Normal file
@ -0,0 +1,150 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <vulkan/vulkan.hpp>
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
namespace TextureUnits {
|
||||
|
||||
struct TextureUnit {
|
||||
GLint id;
|
||||
constexpr GLenum Enum() const {
|
||||
return static_cast<GLenum>(GL_TEXTURE0 + id);
|
||||
}
|
||||
};
|
||||
|
||||
constexpr TextureUnit PicaTexture(int unit) {
|
||||
return TextureUnit{unit};
|
||||
}
|
||||
|
||||
constexpr TextureUnit TextureCube{6};
|
||||
constexpr TextureUnit TextureBufferLUT_LF{3};
|
||||
constexpr TextureUnit TextureBufferLUT_RG{4};
|
||||
constexpr TextureUnit TextureBufferLUT_RGBA{5};
|
||||
|
||||
} // namespace TextureUnits
|
||||
|
||||
namespace ImageUnits {
|
||||
constexpr uint ShadowBuffer = 0;
|
||||
constexpr uint ShadowTexturePX = 1;
|
||||
constexpr uint ShadowTextureNX = 2;
|
||||
constexpr uint ShadowTexturePY = 3;
|
||||
constexpr uint ShadowTextureNY = 4;
|
||||
constexpr uint ShadowTexturePZ = 5;
|
||||
constexpr uint ShadowTextureNZ = 6;
|
||||
} // namespace ImageUnits
|
||||
|
||||
class VulkanState {
|
||||
public:
|
||||
struct {
|
||||
bool enabled;
|
||||
vk::CullModeFlags mode;
|
||||
vk::FrontFace front_face;
|
||||
} cull;
|
||||
|
||||
struct {
|
||||
bool test_enabled;
|
||||
vk::CompareOp test_func;
|
||||
bool write_mask;
|
||||
} depth;
|
||||
|
||||
vk::ColorComponentFlags color_mask;
|
||||
|
||||
struct {
|
||||
bool test_enabled;
|
||||
vk::CompareOp test_func;
|
||||
int test_ref;
|
||||
uint32_t test_mask, write_mask;
|
||||
vk::StencilOp action_stencil_fail;
|
||||
vk::StencilOp action_depth_fail;
|
||||
vk::StencilOp action_depth_pass;
|
||||
} stencil;
|
||||
|
||||
vk::LogicOp logic_op;
|
||||
|
||||
// 3 texture units - one for each that is used in PICA fragment shader emulation
|
||||
struct TextureUnit {
|
||||
uint texture_2d; // GL_TEXTURE_BINDING_2D
|
||||
uint sampler; // GL_SAMPLER_BINDING
|
||||
};
|
||||
std::array<TextureUnit, 3> texture_units;
|
||||
|
||||
struct {
|
||||
uint texture_cube; // GL_TEXTURE_BINDING_CUBE_MAP
|
||||
uint sampler; // GL_SAMPLER_BINDING
|
||||
} texture_cube_unit;
|
||||
|
||||
struct {
|
||||
uint texture_buffer; // GL_TEXTURE_BINDING_BUFFER
|
||||
} texture_buffer_lut_lf;
|
||||
|
||||
struct {
|
||||
uint texture_buffer; // GL_TEXTURE_BINDING_BUFFER
|
||||
} texture_buffer_lut_rg;
|
||||
|
||||
struct {
|
||||
uint texture_buffer; // GL_TEXTURE_BINDING_BUFFER
|
||||
} texture_buffer_lut_rgba;
|
||||
|
||||
// GL_IMAGE_BINDING_NAME
|
||||
uint image_shadow_buffer;
|
||||
uint image_shadow_texture_px;
|
||||
uint image_shadow_texture_nx;
|
||||
uint image_shadow_texture_py;
|
||||
uint image_shadow_texture_ny;
|
||||
uint image_shadow_texture_pz;
|
||||
uint image_shadow_texture_nz;
|
||||
|
||||
struct {
|
||||
uint read_framebuffer; // GL_READ_FRAMEBUFFER_BINDING
|
||||
uint draw_framebuffer; // GL_DRAW_FRAMEBUFFER_BINDING
|
||||
uint vertex_array; // GL_VERTEX_ARRAY_BINDING
|
||||
uint vertex_buffer; // GL_ARRAY_BUFFER_BINDING
|
||||
uint uniform_buffer; // GL_UNIFORM_BUFFER_BINDING
|
||||
uint shader_program; // GL_CURRENT_PROGRAM
|
||||
uint program_pipeline; // GL_PROGRAM_PIPELINE_BINDING
|
||||
} draw;
|
||||
|
||||
struct {
|
||||
bool enabled; // GL_SCISSOR_TEST
|
||||
int x, y;
|
||||
std::size_t width, height;
|
||||
} scissor;
|
||||
|
||||
struct {
|
||||
int x, y;
|
||||
std::size_t width, height;
|
||||
} viewport;
|
||||
|
||||
std::array<bool, 2> clip_distance;
|
||||
|
||||
VulkanState();
|
||||
|
||||
/// Get the currently active OpenGL state
|
||||
static VulkanState GetCurState() {
|
||||
return cur_state;
|
||||
}
|
||||
|
||||
/// Apply this state as the current OpenGL state
|
||||
void Apply() const;
|
||||
|
||||
/// Resets any references to the given resource
|
||||
VulkanState& ResetTexture(GLuint handle);
|
||||
VulkanState& ResetSampler(GLuint handle);
|
||||
VulkanState& ResetProgram(GLuint handle);
|
||||
VulkanState& ResetPipeline(GLuint handle);
|
||||
VulkanState& ResetBuffer(GLuint handle);
|
||||
VulkanState& ResetVertexArray(GLuint handle);
|
||||
VulkanState& ResetFramebuffer(GLuint handle);
|
||||
VulkanState& ResetRenderbuffer(GLuint handle);
|
||||
|
||||
private:
|
||||
static VulkanState cur_state;
|
||||
};
|
||||
|
||||
} // namespace OpenGL
|
435
src/video_core/renderer_vulkan/vk_swapchain.cpp
Normal file
435
src/video_core/renderer_vulkan/vk_swapchain.cpp
Normal file
@ -0,0 +1,435 @@
|
||||
#include "vk_swapchain.h"
|
||||
#include "vk_context.h"
|
||||
#include "vk_buffer.h"
|
||||
#include <fmt/core.h>
|
||||
|
||||
constexpr uint64_t MAX_UINT64 = ~0ULL;
|
||||
|
||||
VkWindow::VkWindow(int width, int height, std::string_view name) :
|
||||
width(width), height(height), name(name)
|
||||
{
|
||||
glfwInit();
|
||||
glfwWindowHint(GLFW_CLIENT_API, GL_FALSE);
|
||||
glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);
|
||||
|
||||
window = glfwCreateWindow(width, height, name.data(), nullptr, nullptr);
|
||||
glfwSetWindowUserPointer(window, this);
|
||||
glfwSetFramebufferSizeCallback(window, [](GLFWwindow* window, int width, int height)
|
||||
{
|
||||
auto my_window = reinterpret_cast<VkWindow*>(glfwGetWindowUserPointer(window));
|
||||
my_window->framebuffer_resized = true;
|
||||
my_window->width = width;
|
||||
my_window->height = height;
|
||||
});
|
||||
}
|
||||
|
||||
VkWindow::~VkWindow()
|
||||
{
|
||||
auto& device = context->device;
|
||||
device->waitIdle();
|
||||
|
||||
buffers.clear();
|
||||
glfwDestroyWindow(window);
|
||||
glfwTerminate();
|
||||
}
|
||||
|
||||
bool VkWindow::should_close() const
|
||||
{
|
||||
return glfwWindowShouldClose(window);
|
||||
}
|
||||
|
||||
vk::Extent2D VkWindow::get_extent() const
|
||||
{
|
||||
return { width, height };
|
||||
}
|
||||
|
||||
void VkWindow::begin_frame()
|
||||
{
|
||||
// Poll for mouse events
|
||||
glfwPollEvents();
|
||||
|
||||
auto& device = context->device;
|
||||
if (auto result = device->waitForFences(flight_fences[current_frame].get(), true, MAX_UINT64); result != vk::Result::eSuccess)
|
||||
throw std::runtime_error("[VK] Failed waiting for flight fences");
|
||||
|
||||
device->resetFences(flight_fences[current_frame].get());
|
||||
try
|
||||
{
|
||||
vk::ResultValue result = device->acquireNextImageKHR(swapchain.get(), MAX_UINT64, image_semaphores[current_frame].get(), nullptr);
|
||||
image_index = result.value;
|
||||
}
|
||||
catch (vk::OutOfDateKHRError err)
|
||||
{
|
||||
//recreateSwapChain();
|
||||
return;
|
||||
}
|
||||
catch (vk::SystemError err)
|
||||
{
|
||||
throw std::runtime_error("failed to acquire swap chain image!");
|
||||
}
|
||||
|
||||
// Start command buffer recording
|
||||
auto& command_buffer = context->get_command_buffer();
|
||||
command_buffer.begin({ vk::CommandBufferUsageFlagBits::eSimultaneousUse });
|
||||
|
||||
// Clear the screen
|
||||
vk::ClearValue clear_values[2];
|
||||
clear_values[0].color = { std::array<float, 4>{ 0.0f, 0.0f, 0.0f, 1.0f } };
|
||||
clear_values[1].depthStencil = vk::ClearDepthStencilValue(0.0f, 0.0f);
|
||||
|
||||
vk::Rect2D render_area({0, 0}, swapchain_info.extent);
|
||||
vk::RenderPassBeginInfo renderpass_info(context->renderpass.get(), buffers[current_frame].framebuffer, render_area, 2, clear_values);
|
||||
|
||||
command_buffer.beginRenderPass(renderpass_info, vk::SubpassContents::eInline);
|
||||
command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, context->graphics_pipeline.get());
|
||||
command_buffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, context->pipeline_layout.get(), 0,
|
||||
context->descriptor_sets[current_frame], {});
|
||||
command_buffer.setDepthCompareOp(vk::CompareOp::eGreaterOrEqual);
|
||||
}
|
||||
|
||||
void VkWindow::end_frame()
|
||||
{
|
||||
// Finish recording
|
||||
auto& command_buffer = context->get_command_buffer();
|
||||
command_buffer.endRenderPass();
|
||||
command_buffer.end();
|
||||
|
||||
std::array<vk::PipelineStageFlags, 1> wait_stages = { vk::PipelineStageFlagBits::eColorAttachmentOutput };
|
||||
std::array<vk::CommandBuffer, 1> command_buffers = { context->get_command_buffer() };
|
||||
|
||||
submit_info = vk::SubmitInfo(image_semaphores[current_frame].get(), wait_stages, command_buffers, render_semaphores[current_frame].get());
|
||||
context->graphics_queue.submit(submit_info, flight_fences[current_frame].get());
|
||||
|
||||
vk::PresentInfoKHR present_info(render_semaphores[current_frame].get(), swapchain.get(), image_index);
|
||||
vk::Result result;
|
||||
try
|
||||
{
|
||||
result = present_queue.presentKHR(present_info);
|
||||
}
|
||||
catch (vk::OutOfDateKHRError err)
|
||||
{
|
||||
result = vk::Result::eErrorOutOfDateKHR;
|
||||
}
|
||||
catch (vk::SystemError err)
|
||||
{
|
||||
throw std::runtime_error("failed to present swap chain image!");
|
||||
}
|
||||
|
||||
if (result == vk::Result::eSuboptimalKHR || result == vk::Result::eSuboptimalKHR || framebuffer_resized)
|
||||
{
|
||||
framebuffer_resized = false;
|
||||
// recreate_swapchain();
|
||||
return;
|
||||
}
|
||||
|
||||
current_frame = (current_frame + 1) % MAX_FRAMES_IN_FLIGHT;
|
||||
}
|
||||
|
||||
std::shared_ptr<VkContext> VkWindow::create_context(bool validation)
|
||||
{
|
||||
vk::ApplicationInfo app_info("PS2 Emulator", 1, nullptr, 0, VK_API_VERSION_1_3);
|
||||
|
||||
uint32_t extension_count = 0U;
|
||||
const char** extension_list = glfwGetRequiredInstanceExtensions(&extension_count);
|
||||
|
||||
// Get required extensions
|
||||
std::vector<const char*> extensions(extension_list, extension_list + extension_count);
|
||||
extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME);
|
||||
extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
|
||||
|
||||
const char* layers[1] = { "VK_LAYER_KHRONOS_validation" };
|
||||
vk::InstanceCreateInfo instance_info({}, &app_info, {}, {}, extensions.size(), extensions.data());
|
||||
if (validation)
|
||||
{
|
||||
instance_info.enabledLayerCount = 1;
|
||||
instance_info.ppEnabledLayerNames = layers;
|
||||
}
|
||||
|
||||
auto instance = vk::createInstanceUnique(instance_info);
|
||||
|
||||
// Create a surface for our window
|
||||
VkSurfaceKHR surface_tmp;
|
||||
if (glfwCreateWindowSurface(instance.get(), window, nullptr, &surface_tmp) != VK_SUCCESS)
|
||||
throw std::runtime_error("[WINDOW] Could not create window surface\n");
|
||||
|
||||
surface = vk::UniqueSurfaceKHR(surface_tmp);
|
||||
|
||||
// Create context
|
||||
context = std::make_shared<VkContext>(std::move(instance), this);
|
||||
swapchain_info = get_swapchain_info();
|
||||
context->create(swapchain_info);
|
||||
|
||||
// Create swapchain
|
||||
create_present_queue();
|
||||
create_depth_buffer();
|
||||
create_swapchain();
|
||||
create_sync_objects();
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
void VkWindow::create_present_queue()
|
||||
{
|
||||
auto& physical_device = context->physical_device;
|
||||
auto family_props = physical_device.getQueueFamilyProperties();
|
||||
|
||||
// Determine a queueFamilyIndex that suports present
|
||||
// first check if the graphicsQueueFamiliyIndex is good enough
|
||||
size_t present_queue_family = -1;
|
||||
if (physical_device.getSurfaceSupportKHR(context->queue_family, surface.get()))
|
||||
{
|
||||
present_queue_family = context->queue_family;
|
||||
}
|
||||
else
|
||||
{
|
||||
// The graphicsQueueFamilyIndex doesn't support present -> look for an other family index that supports both
|
||||
// graphics and present
|
||||
vk::QueueFlags search = vk::QueueFlagBits::eGraphics | vk::QueueFlagBits::eCompute;
|
||||
for (size_t i = 0; i < family_props.size(); i++ )
|
||||
{
|
||||
if (((family_props[i].queueFlags & search) == search) && physical_device.getSurfaceSupportKHR(i, surface.get()))
|
||||
{
|
||||
context->queue_family = present_queue_family = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (present_queue_family == -1)
|
||||
{
|
||||
// There's nothing like a single family index that supports both graphics and present -> look for an other
|
||||
// family index that supports present
|
||||
for (size_t i = 0; i < family_props.size(); i++ )
|
||||
{
|
||||
if (physical_device.getSurfaceSupportKHR(i, surface.get()))
|
||||
{
|
||||
present_queue_family = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (present_queue_family == -1)
|
||||
throw std::runtime_error("[VK] No present queue could be found");
|
||||
|
||||
// Get the queue
|
||||
present_queue = context->device->getQueue(present_queue_family, 0);
|
||||
}
|
||||
|
||||
void VkWindow::create_swapchain(bool enable_vsync)
|
||||
{
|
||||
auto& physical_device = context->physical_device;
|
||||
vk::SwapchainKHR old_swapchain = swapchain.get();
|
||||
|
||||
// Figure out best swapchain create attributes
|
||||
auto capabilities = physical_device.getSurfaceCapabilitiesKHR(surface.get());
|
||||
|
||||
// Find the transformation of the surface, prefer a non-rotated transform
|
||||
auto pretransform = capabilities.supportedTransforms & vk::SurfaceTransformFlagBitsKHR::eIdentity ?
|
||||
vk::SurfaceTransformFlagBitsKHR::eIdentity :
|
||||
capabilities.currentTransform;
|
||||
|
||||
// Create the swapchain
|
||||
vk::SwapchainCreateInfoKHR swapchain_create_info
|
||||
(
|
||||
{},
|
||||
surface.get(),
|
||||
swapchain_info.image_count,
|
||||
swapchain_info.surface_format.format,
|
||||
swapchain_info.surface_format.colorSpace,
|
||||
swapchain_info.extent,
|
||||
1,
|
||||
vk::ImageUsageFlagBits::eColorAttachment,
|
||||
vk::SharingMode::eExclusive,
|
||||
0,
|
||||
nullptr,
|
||||
pretransform,
|
||||
vk::CompositeAlphaFlagBitsKHR::eOpaque,
|
||||
swapchain_info.present_mode,
|
||||
VK_TRUE,
|
||||
old_swapchain
|
||||
);
|
||||
|
||||
auto& device = context->device;
|
||||
swapchain = device->createSwapchainKHRUnique(swapchain_create_info);
|
||||
|
||||
// If an existing sawp chain is re-created, destroy the old swap chain
|
||||
// This also cleans up all the presentable images
|
||||
if (old_swapchain)
|
||||
{
|
||||
buffers.clear();
|
||||
device->destroySwapchainKHR(old_swapchain);
|
||||
}
|
||||
|
||||
// Get the swap chain images
|
||||
auto images = device->getSwapchainImagesKHR(swapchain.get());
|
||||
|
||||
// Create the swapchain buffers containing the image and imageview
|
||||
buffers.resize(images.size());
|
||||
for (size_t i = 0; i < buffers.size(); i++)
|
||||
{
|
||||
vk::ImageViewCreateInfo color_attachment_view
|
||||
(
|
||||
{},
|
||||
images[i],
|
||||
vk::ImageViewType::e2D,
|
||||
swapchain_info.surface_format.format,
|
||||
{},
|
||||
{ vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 }
|
||||
);
|
||||
|
||||
auto image_view = device->createImageView(color_attachment_view);
|
||||
vk::ImageView attachments[] = { image_view, depth_buffer.view };
|
||||
|
||||
vk::FramebufferCreateInfo framebuffer_info
|
||||
(
|
||||
{},
|
||||
context->renderpass.get(),
|
||||
2,
|
||||
attachments,
|
||||
swapchain_info.extent.width,
|
||||
swapchain_info.extent.height,
|
||||
1
|
||||
);
|
||||
|
||||
buffers[i].image = images[i];
|
||||
buffers[i].view = device->createImageView(color_attachment_view);
|
||||
buffers[i].framebuffer = device->createFramebuffer(framebuffer_info);
|
||||
buffers[i].device = &context->device.get();
|
||||
}
|
||||
}
|
||||
|
||||
vk::Framebuffer VkWindow::get_framebuffer(int index) const
|
||||
{
|
||||
return buffers[index].framebuffer;
|
||||
}
|
||||
|
||||
void VkWindow::create_depth_buffer()
|
||||
{
|
||||
auto& device = context->device;
|
||||
|
||||
// Create an optimal image used as the depth stencil attachment
|
||||
vk::ImageCreateInfo image
|
||||
(
|
||||
{},
|
||||
vk::ImageType::e2D,
|
||||
swapchain_info.depth_format,
|
||||
vk::Extent3D(swapchain_info.extent, 1),
|
||||
1, 1,
|
||||
vk::SampleCountFlagBits::e1,
|
||||
vk::ImageTiling::eOptimal,
|
||||
vk::ImageUsageFlagBits::eDepthStencilAttachment
|
||||
);
|
||||
|
||||
depth_buffer.image = device->createImage(image);
|
||||
|
||||
// Allocate memory for the image (device local) and bind it to our image
|
||||
auto requirements = device->getImageMemoryRequirements(depth_buffer.image);
|
||||
auto memory_type_index = Buffer::find_memory_type(requirements.memoryTypeBits, vk::MemoryPropertyFlagBits::eDeviceLocal, context);
|
||||
vk::MemoryAllocateInfo memory_alloc(requirements.size, memory_type_index);
|
||||
|
||||
depth_buffer.memory = device->allocateMemory(memory_alloc);
|
||||
device->bindImageMemory(depth_buffer.image, depth_buffer.memory, 0);
|
||||
|
||||
// Create a view for the depth stencil image
|
||||
vk::ImageViewCreateInfo depth_view
|
||||
(
|
||||
{},
|
||||
depth_buffer.image,
|
||||
vk::ImageViewType::e2D,
|
||||
swapchain_info.depth_format,
|
||||
{},
|
||||
vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil, 0, 1, 0, 1)
|
||||
);
|
||||
depth_buffer.view = device->createImageView(depth_view);
|
||||
}
|
||||
|
||||
SwapchainInfo VkWindow::get_swapchain_info() const
|
||||
{
|
||||
SwapchainInfo info;
|
||||
auto& physical_device = context->physical_device;
|
||||
|
||||
// Choose surface format
|
||||
auto formats = physical_device.getSurfaceFormatsKHR(surface.get());
|
||||
info.surface_format = formats[0];
|
||||
|
||||
if (formats.size() == 1 && formats[0].format == vk::Format::eUndefined)
|
||||
{
|
||||
info.surface_format = { vk::Format::eB8G8R8A8Unorm, vk::ColorSpaceKHR::eSrgbNonlinear };
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const auto& format : formats)
|
||||
{
|
||||
if (format.format == vk::Format::eB8G8R8A8Unorm &&
|
||||
format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear)
|
||||
{
|
||||
info.surface_format = format;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Choose best present mode
|
||||
auto present_modes = physical_device.getSurfacePresentModesKHR(surface.get());
|
||||
info.present_mode = vk::PresentModeKHR::eFifo;
|
||||
|
||||
// Query surface capabilities
|
||||
auto capabilities = physical_device.getSurfaceCapabilitiesKHR(surface.get());
|
||||
info.extent = capabilities.currentExtent;
|
||||
|
||||
if (capabilities.currentExtent.width == std::numeric_limits<uint32_t>::max())
|
||||
{
|
||||
int width, height;
|
||||
glfwGetFramebufferSize(window, &width, &height);
|
||||
|
||||
vk::Extent2D extent = { static_cast<uint32_t>(width), static_cast<uint32_t>(height) };
|
||||
extent.width = std::max(capabilities.minImageExtent.width, std::min(capabilities.maxImageExtent.width, extent.width));
|
||||
extent.height = std::max(capabilities.minImageExtent.height, std::min(capabilities.maxImageExtent.height, extent.height));
|
||||
|
||||
info.extent = extent;
|
||||
}
|
||||
|
||||
// Find a suitable depth (stencil) format that is supported by the device
|
||||
auto depth_formats = { vk::Format::eD32SfloatS8Uint, vk::Format::eD24UnormS8Uint, vk::Format::eD16UnormS8Uint,
|
||||
vk::Format::eD32Sfloat, vk::Format::eD16Unorm };
|
||||
info.depth_format = vk::Format::eUndefined;
|
||||
|
||||
for (auto& format : depth_formats)
|
||||
{
|
||||
auto format_props = physical_device.getFormatProperties(format);
|
||||
auto search = vk::FormatFeatureFlagBits::eDepthStencilAttachment;
|
||||
if ((format_props.optimalTilingFeatures & search) == search)
|
||||
{
|
||||
info.depth_format = format;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (info.depth_format == vk::Format::eUndefined)
|
||||
throw std::runtime_error("[VK] Couldn't find optinal depth format");
|
||||
|
||||
// Determine the number of images
|
||||
info.image_count = capabilities.minImageCount + 1 > capabilities.maxImageCount &&
|
||||
capabilities.maxImageCount > 0 ?
|
||||
capabilities.maxImageCount :
|
||||
capabilities.minImageCount + 1;
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
void VkWindow::create_sync_objects()
|
||||
{
|
||||
auto& device = context->device;
|
||||
|
||||
image_semaphores.resize(MAX_FRAMES_IN_FLIGHT);
|
||||
render_semaphores.resize(MAX_FRAMES_IN_FLIGHT);
|
||||
flight_fences.resize(MAX_FRAMES_IN_FLIGHT);
|
||||
|
||||
for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++)
|
||||
{
|
||||
image_semaphores[i] = device->createSemaphoreUnique({});
|
||||
render_semaphores[i] = device->createSemaphoreUnique({});
|
||||
flight_fences[i] = device->createFenceUnique({ vk::FenceCreateFlagBits::eSignaled });
|
||||
}
|
||||
}
|
100
src/video_core/renderer_vulkan/vk_swapchain.h
Normal file
100
src/video_core/renderer_vulkan/vk_swapchain.h
Normal file
@ -0,0 +1,100 @@
|
||||
#pragma once
|
||||
#include <vulkan/vulkan.hpp>
|
||||
#include <string_view>
|
||||
#include <memory>
|
||||
|
||||
class VkContext;
|
||||
struct GLFWwindow;
|
||||
|
||||
struct SwapchainBuffer
|
||||
{
|
||||
~SwapchainBuffer()
|
||||
{
|
||||
device->destroyImageView(view);
|
||||
device->destroyFramebuffer(framebuffer);
|
||||
}
|
||||
|
||||
vk::Image image;
|
||||
vk::ImageView view;
|
||||
vk::Framebuffer framebuffer;
|
||||
vk::Device* device;
|
||||
};
|
||||
|
||||
struct SwapchainInfo
|
||||
{
|
||||
vk::Format depth_format;
|
||||
vk::SurfaceFormatKHR surface_format;
|
||||
vk::PresentModeKHR present_mode;
|
||||
vk::Extent2D extent;
|
||||
uint32_t image_count;
|
||||
};
|
||||
|
||||
struct DepthBuffer : public NonCopyable
|
||||
{
|
||||
~DepthBuffer()
|
||||
{
|
||||
// Destroy depth buffer
|
||||
device.destroyImage(image);
|
||||
device.destroyImageView(view);
|
||||
device.freeMemory(memory);
|
||||
}
|
||||
|
||||
vk::Device device;
|
||||
vk::Image image;
|
||||
vk::DeviceMemory memory;
|
||||
vk::ImageView view;
|
||||
};
|
||||
|
||||
constexpr int MAX_FRAMES_IN_FLIGHT = 3;
|
||||
|
||||
class VkWindow
|
||||
{
|
||||
public:
|
||||
VkWindow(int width, int height, std::string_view name);
|
||||
~VkWindow();
|
||||
|
||||
std::shared_ptr<VkContext> create_context(bool validation = true);
|
||||
bool should_close() const;
|
||||
vk::Extent2D get_extent() const;
|
||||
|
||||
void begin_frame();
|
||||
void end_frame();
|
||||
|
||||
void destroy();
|
||||
vk::Framebuffer get_framebuffer(int index) const;
|
||||
|
||||
private:
|
||||
void create_sync_objects();
|
||||
void create_swapchain(bool enable_vsync = false);
|
||||
SwapchainInfo get_swapchain_info() const;
|
||||
void create_depth_buffer();
|
||||
void create_present_queue();
|
||||
|
||||
public:
|
||||
// Window attributes
|
||||
GLFWwindow* window;
|
||||
uint32_t width = 0, height = 0;
|
||||
bool framebuffer_resized = false;
|
||||
std::string_view name;
|
||||
|
||||
// Context
|
||||
std::shared_ptr<VkContext> context;
|
||||
vk::Queue present_queue;
|
||||
|
||||
// Swapchain objects
|
||||
vk::UniqueSurfaceKHR surface;
|
||||
vk::UniqueSwapchainKHR swapchain;
|
||||
std::vector<SwapchainBuffer> buffers;
|
||||
SwapchainInfo swapchain_info;
|
||||
uint32_t current_frame = 0, image_index = 0;
|
||||
uint32_t draw_batch = 0;
|
||||
|
||||
// Depth buffer
|
||||
DepthBuffer depth_buffer;
|
||||
|
||||
// Synchronization
|
||||
vk::SubmitInfo submit_info;
|
||||
std::vector<vk::UniqueSemaphore> image_semaphores;
|
||||
std::vector<vk::UniqueSemaphore> render_semaphores;
|
||||
std::vector<vk::UniqueFence> flight_fences;
|
||||
};
|
147
src/video_core/renderer_vulkan/vk_texture.cpp
Normal file
147
src/video_core/renderer_vulkan/vk_texture.cpp
Normal file
@ -0,0 +1,147 @@
|
||||
#include "vk_texture.h"
|
||||
#include "vk_buffer.h"
|
||||
#include "vk_context.h"
|
||||
|
||||
VkTexture::VkTexture(const std::shared_ptr<VkContext>& context) :
|
||||
context(context), staging(context)
|
||||
{
|
||||
}
|
||||
|
||||
void VkTexture::create(int width_, int height_, vk::ImageType type, vk::Format format_)
|
||||
{
|
||||
auto& device = context->device;
|
||||
format = format_; width = width_; height = height_;
|
||||
|
||||
switch (format)
|
||||
{
|
||||
case vk::Format::eR8G8B8A8Uint:
|
||||
case vk::Format::eR8G8B8A8Srgb:
|
||||
case vk::Format::eR32Uint:
|
||||
channels = 4;
|
||||
break;
|
||||
case vk::Format::eR8G8B8Uint:
|
||||
channels = 3;
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error("[VK] Unknown texture format");
|
||||
}
|
||||
|
||||
int image_size = width * height * channels;
|
||||
staging.create(image_size, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent,
|
||||
vk::BufferUsageFlagBits::eTransferSrc);
|
||||
pixels = staging.memory;
|
||||
|
||||
// Create the texture
|
||||
vk::ImageCreateInfo image_info
|
||||
(
|
||||
{},
|
||||
type,
|
||||
format,
|
||||
{ width, height, 1 }, 1, 1,
|
||||
vk::SampleCountFlagBits::e1,
|
||||
vk::ImageTiling::eOptimal,
|
||||
vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled
|
||||
);
|
||||
|
||||
texture = device->createImageUnique(image_info);
|
||||
|
||||
// Create texture memory
|
||||
auto requirements = device->getImageMemoryRequirements(texture.get());
|
||||
auto memory_index = Buffer::find_memory_type(requirements.memoryTypeBits, vk::MemoryPropertyFlagBits::eDeviceLocal, context);
|
||||
vk::MemoryAllocateInfo alloc_info(requirements.size, memory_index);
|
||||
|
||||
texture_memory = device->allocateMemoryUnique(alloc_info);
|
||||
device->bindImageMemory(texture.get(), texture_memory.get(), 0);
|
||||
|
||||
// Create texture view
|
||||
vk::ImageViewCreateInfo view_info({}, texture.get(), vk::ImageViewType::e1D, format, {},
|
||||
vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1));
|
||||
texture_view = device->createImageViewUnique(view_info);
|
||||
|
||||
// Create texture sampler
|
||||
auto props = context->physical_device.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);
|
||||
|
||||
texture_sampler = device->createSamplerUnique(sampler_info);
|
||||
}
|
||||
|
||||
void VkTexture::transition_layout(vk::ImageLayout old_layout, vk::ImageLayout new_layout)
|
||||
{
|
||||
auto& device = context->device;
|
||||
auto& queue = context->graphics_queue;
|
||||
|
||||
vk::CommandBufferAllocateInfo alloc_info(context->command_pool.get(), vk::CommandBufferLevel::ePrimary, 1);
|
||||
vk::CommandBuffer command_buffer = device->allocateCommandBuffers(alloc_info)[0];
|
||||
|
||||
command_buffer.begin({vk::CommandBufferUsageFlagBits::eOneTimeSubmit});
|
||||
|
||||
vk::ImageMemoryBarrier barrier({}, {}, old_layout, new_layout, VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, texture.get(),
|
||||
vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1));
|
||||
std::array<vk::ImageMemoryBarrier, 1> barriers = { barrier };
|
||||
|
||||
vk::PipelineStageFlags source_stage, destination_stage;
|
||||
if (old_layout == vk::ImageLayout::eUndefined && new_layout == vk::ImageLayout::eTransferDstOptimal)
|
||||
{
|
||||
barrier.srcAccessMask = vk::AccessFlagBits::eNone;
|
||||
barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite;
|
||||
|
||||
source_stage = vk::PipelineStageFlagBits::eTopOfPipe;
|
||||
destination_stage = vk::PipelineStageFlagBits::eTransfer;
|
||||
}
|
||||
else if (old_layout == vk::ImageLayout::eTransferDstOptimal && new_layout == vk::ImageLayout::eShaderReadOnlyOptimal)
|
||||
{
|
||||
barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
|
||||
barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;
|
||||
|
||||
source_stage = vk::PipelineStageFlagBits::eTransfer;
|
||||
destination_stage = vk::PipelineStageFlagBits::eFragmentShader;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::invalid_argument("[VK] Unsupported layout transition!");
|
||||
}
|
||||
|
||||
command_buffer.pipelineBarrier(source_stage, destination_stage, vk::DependencyFlagBits::eByRegion, {}, {}, barriers);
|
||||
command_buffer.end();
|
||||
|
||||
vk::SubmitInfo submit_info({}, {}, {}, 1, &command_buffer);
|
||||
queue.submit(submit_info, nullptr);
|
||||
queue.waitIdle();
|
||||
|
||||
device->freeCommandBuffers(context->command_pool.get(), command_buffer);
|
||||
}
|
||||
|
||||
void VkTexture::copy_pixels(uint8_t* new_pixels, uint32_t count)
|
||||
{
|
||||
auto& device = context->device;
|
||||
auto& queue = context->graphics_queue;
|
||||
|
||||
// Transition image to transfer format
|
||||
transition_layout(vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal);
|
||||
|
||||
// Copy pixels to staging buffer
|
||||
std::memcpy(pixels, new_pixels, count * channels);
|
||||
|
||||
// Copy the staging buffer to the image
|
||||
vk::CommandBufferAllocateInfo alloc_info(context->command_pool.get(), vk::CommandBufferLevel::ePrimary, 1);
|
||||
vk::CommandBuffer command_buffer = device->allocateCommandBuffers(alloc_info)[0];
|
||||
|
||||
command_buffer.begin({vk::CommandBufferUsageFlagBits::eOneTimeSubmit});
|
||||
|
||||
vk::BufferImageCopy region(0, 0, 0, vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, 0, 0, 1), {0}, {width,height,1});
|
||||
std::array<vk::BufferImageCopy, 1> regions = { region };
|
||||
|
||||
command_buffer.copyBufferToImage(staging.buffer.get(), texture.get(), vk::ImageLayout::eTransferDstOptimal, regions);
|
||||
command_buffer.end();
|
||||
|
||||
vk::SubmitInfo submit_info({}, {}, {}, 1, &command_buffer);
|
||||
queue.submit(submit_info, nullptr);
|
||||
queue.waitIdle();
|
||||
|
||||
device->freeCommandBuffers(context->command_pool.get(), command_buffer);
|
||||
|
||||
// Prepare for shader reads
|
||||
transition_layout(vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal);
|
||||
}
|
34
src/video_core/renderer_vulkan/vk_texture.h
Normal file
34
src/video_core/renderer_vulkan/vk_texture.h
Normal file
@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
#include "vk_buffer.h"
|
||||
#include <memory>
|
||||
#include <span>
|
||||
|
||||
class VkContext;
|
||||
|
||||
class VkTexture : public NonCopyable, public Resource
|
||||
{
|
||||
friend class VkContext;
|
||||
public:
|
||||
VkTexture(const std::shared_ptr<VkContext>& context);
|
||||
~VkTexture() = default;
|
||||
|
||||
void create(int width, int height, vk::ImageType type, vk::Format format = vk::Format::eR8G8B8A8Uint);
|
||||
void copy_pixels(std::span<u8> pixels);
|
||||
|
||||
private:
|
||||
void transition_layout(vk::ImageLayout old_layout, vk::ImageLayout new_layout);
|
||||
|
||||
private:
|
||||
// Texture buffer
|
||||
void* pixels = nullptr;
|
||||
std::shared_ptr<VkContext> context;
|
||||
uint32_t width = 0, height = 0, channels = 0;
|
||||
Buffer staging;
|
||||
|
||||
// Texture objects
|
||||
vk::UniqueImage texture;
|
||||
vk::UniqueImageView texture_view;
|
||||
vk::UniqueDeviceMemory texture_memory;
|
||||
vk::UniqueSampler texture_sampler;
|
||||
vk::Format format;
|
||||
};
|
Reference in New Issue
Block a user