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:
GPUCode
2022-04-26 21:20:31 +03:00
parent 593b0b2d8f
commit 32bab7d1ba
18 changed files with 4050 additions and 2 deletions

View File

@ -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)

View File

@ -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

View File

@ -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()

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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);
}

View 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;
};

View 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);
}

View 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;
};

View 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

View 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

View 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

View 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

View 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 });
}
}

View 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;
};

View 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);
}

View 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;
};