diff --git a/CMakeLists.txt b/CMakeLists.txt index 5f0bee895..cf85c588d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/src/common/logging/log.h b/src/common/logging/log.h index 3b3810851..b9293f8d5 100644 --- a/src/common/logging/log.h +++ b/src/common/logging/log.h @@ -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 diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index bb81f117c..d5796f468 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -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() diff --git a/src/video_core/renderer_vulkan/pica_to_vulkan.h b/src/video_core/renderer_vulkan/pica_to_vulkan.h new file mode 100644 index 000000000..205e76875 --- /dev/null +++ b/src/video_core/renderer_vulkan/pica_to_vulkan.h @@ -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 +#include +#include +#include +#include +#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 filter_table = { vk::Filter::eNearest, vk::Filter::eLinear }; + std::array 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 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(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(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 blend_equation_table{{ + vk::BlendOp::eAdd, + vk::BlendOp::eSubtract, + vk::BlendOp::eReverseSubtract, + vk::BlendOp::eMin, + vk::BlendOp::eMax, + }}; + + const auto index = static_cast(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 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(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 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(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 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(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 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(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 diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp new file mode 100644 index 000000000..4829a5b08 --- /dev/null +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp @@ -0,0 +1,1289 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "common/assert.h" +#include "common/bit_field.h" +#include "common/logging/log.h" +#include "common/microprofile.h" +#include "core/core.h" +#include "core/core_timing.h" +#include "core/dumping/backend.h" +#include "core/frontend/emu_window.h" +#include "core/frontend/framebuffer_layout.h" +#include "core/hw/gpu.h" +#include "core/hw/hw.h" +#include "core/hw/lcd.h" +#include "core/memory.h" +#include "core/settings.h" +#include "core/tracer/recorder.h" +#include "video_core/debug_utils/debug_utils.h" +#include "video_core/rasterizer_interface.h" +#include "video_core/renderer_opengl/gl_state.h" +#include "video_core/renderer_opengl/gl_vars.h" +#include "video_core/renderer_opengl/post_processing_opengl.h" +#include "video_core/renderer_opengl/renderer_opengl.h" +#include "video_core/video_core.h" + +namespace OpenGL { + +// If the size of this is too small, it ends up creating a soft cap on FPS as the renderer will have +// to wait on available presentation frames. There doesn't seem to be much of a downside to a larger +// number but 9 swap textures at 60FPS presentation allows for 800% speed so thats probably fine +#ifdef ANDROID +// Reduce the size of swap_chain, since the UI only allows upto 200% speed. +constexpr std::size_t SWAP_CHAIN_SIZE = 6; +#else +constexpr std::size_t SWAP_CHAIN_SIZE = 9; +#endif + +class OGLTextureMailboxException : public std::runtime_error { +public: + using std::runtime_error::runtime_error; +}; + +class OGLTextureMailbox : public Frontend::TextureMailbox { +public: + std::mutex swap_chain_lock; + std::condition_variable free_cv; + std::condition_variable present_cv; + std::array swap_chain{}; + std::queue free_queue{}; + std::deque present_queue{}; + Frontend::Frame* previous_frame = nullptr; + + OGLTextureMailbox() { + for (auto& frame : swap_chain) { + free_queue.push(&frame); + } + } + + ~OGLTextureMailbox() override { + // lock the mutex and clear out the present and free_queues and notify any people who are + // blocked to prevent deadlock on shutdown + std::scoped_lock lock(swap_chain_lock); + std::queue().swap(free_queue); + present_queue.clear(); + present_cv.notify_all(); + free_cv.notify_all(); + } + + void ReloadPresentFrame(Frontend::Frame* frame, u32 height, u32 width) override { + frame->present.Release(); + frame->present.Create(); + GLint previous_draw_fbo{}; + glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &previous_draw_fbo); + glBindFramebuffer(GL_FRAMEBUFFER, frame->present.handle); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, + frame->color.handle); + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + LOG_CRITICAL(Render_OpenGL, "Failed to recreate present FBO!"); + } + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, previous_draw_fbo); + frame->color_reloaded = false; + } + + void ReloadRenderFrame(Frontend::Frame* frame, u32 width, u32 height) override { + OpenGLState prev_state = OpenGLState::GetCurState(); + OpenGLState state = OpenGLState::GetCurState(); + + // Recreate the color texture attachment + frame->color.Release(); + frame->color.Create(); + state.renderbuffer = frame->color.handle; + state.Apply(); + glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, width, height); + + // Recreate the FBO for the render target + frame->render.Release(); + frame->render.Create(); + state.draw.read_framebuffer = frame->render.handle; + state.draw.draw_framebuffer = frame->render.handle; + state.Apply(); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, + frame->color.handle); + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + LOG_CRITICAL(Render_OpenGL, "Failed to recreate render FBO!"); + } + prev_state.Apply(); + frame->width = width; + frame->height = height; + frame->color_reloaded = true; + } + + Frontend::Frame* GetRenderFrame() override { + std::unique_lock lock(swap_chain_lock); + + // If theres no free frames, we will reuse the oldest render frame + if (free_queue.empty()) { + auto frame = present_queue.back(); + present_queue.pop_back(); + return frame; + } + + Frontend::Frame* frame = free_queue.front(); + free_queue.pop(); + return frame; + } + + void ReleaseRenderFrame(Frontend::Frame* frame) override { + std::unique_lock lock(swap_chain_lock); + present_queue.push_front(frame); + present_cv.notify_one(); + } + + // This is virtual as it is to be overriden in OGLVideoDumpingMailbox below. + virtual void LoadPresentFrame() { + // free the previous frame and add it back to the free queue + if (previous_frame) { + free_queue.push(previous_frame); + free_cv.notify_one(); + } + + // the newest entries are pushed to the front of the queue + Frontend::Frame* frame = present_queue.front(); + present_queue.pop_front(); + // remove all old entries from the present queue and move them back to the free_queue + for (auto f : present_queue) { + free_queue.push(f); + } + present_queue.clear(); + previous_frame = frame; + } + + Frontend::Frame* TryGetPresentFrame(int timeout_ms) override { + std::unique_lock lock(swap_chain_lock); + // wait for new entries in the present_queue + present_cv.wait_for(lock, std::chrono::milliseconds(timeout_ms), + [&] { return !present_queue.empty(); }); + if (present_queue.empty()) { + // timed out waiting for a frame to draw so return the previous frame + return previous_frame; + } + + LoadPresentFrame(); + return previous_frame; + } +}; + +/// This mailbox is different in that it will never discard rendered frames +class OGLVideoDumpingMailbox : public OGLTextureMailbox { +public: + bool quit = false; + + Frontend::Frame* GetRenderFrame() override { + std::unique_lock lock(swap_chain_lock); + + // If theres no free frames, we will wait until one shows up + if (free_queue.empty()) { + free_cv.wait(lock, [&] { return (!free_queue.empty() || quit); }); + if (quit) { + throw OGLTextureMailboxException("VideoDumpingMailbox quitting"); + } + + if (free_queue.empty()) { + LOG_CRITICAL(Render_OpenGL, "Could not get free frame"); + return nullptr; + } + } + + Frontend::Frame* frame = free_queue.front(); + free_queue.pop(); + return frame; + } + + void LoadPresentFrame() override { + // free the previous frame and add it back to the free queue + if (previous_frame) { + free_queue.push(previous_frame); + free_cv.notify_one(); + } + + Frontend::Frame* frame = present_queue.back(); + present_queue.pop_back(); + previous_frame = frame; + + // Do not remove entries from the present_queue, as video dumping would require + // that we preserve all frames + } + + Frontend::Frame* TryGetPresentFrame(int timeout_ms) override { + std::unique_lock lock(swap_chain_lock); + // wait for new entries in the present_queue + present_cv.wait_for(lock, std::chrono::milliseconds(timeout_ms), + [&] { return !present_queue.empty(); }); + if (present_queue.empty()) { + // timed out waiting for a frame + return nullptr; + } + + LoadPresentFrame(); + return previous_frame; + } +}; + +static const char vertex_shader[] = R"( +in vec2 vert_position; +in vec2 vert_tex_coord; +out vec2 frag_tex_coord; + +// This is a truncated 3x3 matrix for 2D transformations: +// The upper-left 2x2 submatrix performs scaling/rotation/mirroring. +// The third column performs translation. +// The third row could be used for projection, which we don't need in 2D. It hence is assumed to +// implicitly be [0, 0, 1] +uniform mat3x2 modelview_matrix; + +void main() { + // Multiply input position by the rotscale part of the matrix and then manually translate by + // the last column. This is equivalent to using a full 3x3 matrix and expanding the vector + // to `vec3(vert_position.xy, 1.0)` + gl_Position = vec4(mat2(modelview_matrix) * vert_position + modelview_matrix[2], 0.0, 1.0); + frag_tex_coord = vert_tex_coord; +} +)"; + +static const char fragment_shader[] = R"( +in vec2 frag_tex_coord; +layout(location = 0) out vec4 color; + +uniform vec4 i_resolution; +uniform vec4 o_resolution; +uniform int layer; + +uniform sampler2D color_texture; + +void main() { + color = texture(color_texture, frag_tex_coord); +} +)"; + +static const char fragment_shader_anaglyph[] = R"( + +// Anaglyph Red-Cyan shader based on Dubois algorithm +// Constants taken from the paper: +// "Conversion of a Stereo Pair to Anaglyph with +// the Least-Squares Projection Method" +// Eric Dubois, March 2009 +const mat3 l = mat3( 0.437, 0.449, 0.164, + -0.062,-0.062,-0.024, + -0.048,-0.050,-0.017); +const mat3 r = mat3(-0.011,-0.032,-0.007, + 0.377, 0.761, 0.009, + -0.026,-0.093, 1.234); + +in vec2 frag_tex_coord; +out vec4 color; + +uniform vec4 resolution; +uniform int layer; + +uniform sampler2D color_texture; +uniform sampler2D color_texture_r; + +void main() { + vec4 color_tex_l = texture(color_texture, frag_tex_coord); + vec4 color_tex_r = texture(color_texture_r, frag_tex_coord); + color = vec4(color_tex_l.rgb*l+color_tex_r.rgb*r, color_tex_l.a); +} +)"; + +static const char fragment_shader_interlaced[] = R"( + +in vec2 frag_tex_coord; +out vec4 color; + +uniform vec4 o_resolution; + +uniform sampler2D color_texture; +uniform sampler2D color_texture_r; + +uniform int reverse_interlaced; + +void main() { + float screen_row = o_resolution.x * frag_tex_coord.x; + if (int(screen_row) % 2 == reverse_interlaced) + color = texture(color_texture, frag_tex_coord); + else + color = texture(color_texture_r, frag_tex_coord); +} +)"; + +/** + * Vertex structure that the drawn screen rectangles are composed of. + */ +struct ScreenRectVertex { + ScreenRectVertex(GLfloat x, GLfloat y, GLfloat u, GLfloat v) { + position[0] = x; + position[1] = y; + tex_coord[0] = u; + tex_coord[1] = v; + } + + GLfloat position[2]; + GLfloat tex_coord[2]; +}; + +/** + * Defines a 1:1 pixel ortographic projection matrix with (0,0) on the top-left + * corner and (width, height) on the lower-bottom. + * + * The projection part of the matrix is trivial, hence these operations are represented + * by a 3x2 matrix. + * + * @param flipped Whether the frame should be flipped upside down. + */ +static std::array MakeOrthographicMatrix(const float width, const float height, + bool flipped) { + + std::array matrix; // Laid out in column-major order + + // Last matrix row is implicitly assumed to be [0, 0, 1]. + if (flipped) { + // clang-format off + matrix[0] = 2.f / width; matrix[2] = 0.f; matrix[4] = -1.f; + matrix[1] = 0.f; matrix[3] = 2.f / height; matrix[5] = -1.f; + // clang-format on + } else { + // clang-format off + matrix[0] = 2.f / width; matrix[2] = 0.f; matrix[4] = -1.f; + matrix[1] = 0.f; matrix[3] = -2.f / height; matrix[5] = 1.f; + // clang-format on + } + + return matrix; +} + +RendererOpenGL::RendererOpenGL(Frontend::EmuWindow& window) + : RendererBase{window}, frame_dumper(Core::System::GetInstance().VideoDumper(), window) { + + window.mailbox = std::make_unique(); + frame_dumper.mailbox = std::make_unique(); +} + +RendererOpenGL::~RendererOpenGL() = default; + +MICROPROFILE_DEFINE(OpenGL_RenderFrame, "OpenGL", "Render Frame", MP_RGB(128, 128, 64)); +MICROPROFILE_DEFINE(OpenGL_WaitPresent, "OpenGL", "Wait For Present", MP_RGB(128, 128, 128)); + +/// Swap buffers (render frame) +void RendererOpenGL::SwapBuffers() { + // Maintain the rasterizer's state as a priority + OpenGLState prev_state = OpenGLState::GetCurState(); + state.Apply(); + + PrepareRendertarget(); + + RenderScreenshot(); + + const auto& layout = render_window.GetFramebufferLayout(); + RenderToMailbox(layout, render_window.mailbox, false); + + if (frame_dumper.IsDumping()) { + try { + RenderToMailbox(frame_dumper.GetLayout(), frame_dumper.mailbox, true); + } catch (const OGLTextureMailboxException& exception) { + LOG_DEBUG(Render_OpenGL, "Frame dumper exception caught: {}", exception.what()); + } + } + + m_current_frame++; + + Core::System::GetInstance().perf_stats->EndSystemFrame(); + + render_window.PollEvents(); + + Core::System::GetInstance().frame_limiter.DoFrameLimiting( + Core::System::GetInstance().CoreTiming().GetGlobalTimeUs()); + Core::System::GetInstance().perf_stats->BeginSystemFrame(); + + prev_state.Apply(); + RefreshRasterizerSetting(); + + if (Pica::g_debug_context && Pica::g_debug_context->recorder) { + Pica::g_debug_context->recorder->FrameFinished(); + } +} + +void RendererOpenGL::RenderScreenshot() { + if (VideoCore::g_renderer_screenshot_requested) { + // Draw this frame to the screenshot framebuffer + screenshot_framebuffer.Create(); + GLuint old_read_fb = state.draw.read_framebuffer; + GLuint old_draw_fb = state.draw.draw_framebuffer; + state.draw.read_framebuffer = state.draw.draw_framebuffer = screenshot_framebuffer.handle; + state.Apply(); + + Layout::FramebufferLayout layout{VideoCore::g_screenshot_framebuffer_layout}; + + GLuint renderbuffer; + glGenRenderbuffers(1, &renderbuffer); + glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer); + glRenderbufferStorage(GL_RENDERBUFFER, GL_RGB8, layout.width, layout.height); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, + renderbuffer); + + DrawScreens(layout, false); + + glReadPixels(0, 0, layout.width, layout.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, + VideoCore::g_screenshot_bits); + + screenshot_framebuffer.Release(); + state.draw.read_framebuffer = old_read_fb; + state.draw.draw_framebuffer = old_draw_fb; + state.Apply(); + glDeleteRenderbuffers(1, &renderbuffer); + + VideoCore::g_screenshot_complete_callback(); + VideoCore::g_renderer_screenshot_requested = false; + } +} + +void RendererOpenGL::PrepareRendertarget() { + for (int i : {0, 1, 2}) { + int fb_id = i == 2 ? 1 : 0; + const auto& framebuffer = GPU::g_regs.framebuffer_config[fb_id]; + + // Main LCD (0): 0x1ED02204, Sub LCD (1): 0x1ED02A04 + u32 lcd_color_addr = + (fb_id == 0) ? LCD_REG_INDEX(color_fill_top) : LCD_REG_INDEX(color_fill_bottom); + lcd_color_addr = HW::VADDR_LCD + 4 * lcd_color_addr; + LCD::Regs::ColorFill color_fill = {0}; + LCD::Read(color_fill.raw, lcd_color_addr); + + if (color_fill.is_enabled) { + LoadColorToActiveGLTexture(color_fill.color_r, color_fill.color_g, color_fill.color_b, + screen_infos[i].texture); + + // Resize the texture in case the framebuffer size has changed + screen_infos[i].texture.width = 1; + screen_infos[i].texture.height = 1; + } else { + if (screen_infos[i].texture.width != (GLsizei)framebuffer.width || + screen_infos[i].texture.height != (GLsizei)framebuffer.height || + screen_infos[i].texture.format != framebuffer.color_format) { + // Reallocate texture if the framebuffer size has changed. + // This is expected to not happen very often and hence should not be a + // performance problem. + ConfigureFramebufferTexture(screen_infos[i].texture, framebuffer); + } + LoadFBToScreenInfo(framebuffer, screen_infos[i], i == 1); + + // Resize the texture in case the framebuffer size has changed + screen_infos[i].texture.width = framebuffer.width; + screen_infos[i].texture.height = framebuffer.height; + } + } +} + +void RendererOpenGL::RenderToMailbox(const Layout::FramebufferLayout& layout, + std::unique_ptr& mailbox, + bool flipped) { + + Frontend::Frame* frame; + { + MICROPROFILE_SCOPE(OpenGL_WaitPresent); + + frame = mailbox->GetRenderFrame(); + + // Clean up sync objects before drawing + + // INTEL driver workaround. We can't delete the previous render sync object until we are + // sure that the presentation is done + if (frame->present_fence) { + glClientWaitSync(frame->present_fence, 0, GL_TIMEOUT_IGNORED); + } + + // delete the draw fence if the frame wasn't presented + if (frame->render_fence) { + glDeleteSync(frame->render_fence); + frame->render_fence = nullptr; + } + + // wait for the presentation to be done + if (frame->present_fence) { + glWaitSync(frame->present_fence, 0, GL_TIMEOUT_IGNORED); + glDeleteSync(frame->present_fence); + frame->present_fence = nullptr; + } + } + + { + MICROPROFILE_SCOPE(OpenGL_RenderFrame); + // Recreate the frame if the size of the window has changed + if (layout.width != frame->width || layout.height != frame->height) { + LOG_DEBUG(Render_OpenGL, "Reloading render frame"); + mailbox->ReloadRenderFrame(frame, layout.width, layout.height); + } + + GLuint render_texture = frame->color.handle; + state.draw.draw_framebuffer = frame->render.handle; + state.Apply(); + DrawScreens(layout, flipped); + // Create a fence for the frontend to wait on and swap this frame to OffTex + frame->render_fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + glFlush(); + mailbox->ReleaseRenderFrame(frame); + } +} + +/** + * Loads framebuffer from emulated memory into the active OpenGL texture. + */ +void RendererOpenGL::LoadFBToScreenInfo(const GPU::Regs::FramebufferConfig& framebuffer, + ScreenInfo& screen_info, bool right_eye) { + + if (framebuffer.address_right1 == 0 || framebuffer.address_right2 == 0) + right_eye = false; + + const PAddr framebuffer_addr = + framebuffer.active_fb == 0 + ? (!right_eye ? framebuffer.address_left1 : framebuffer.address_right1) + : (!right_eye ? framebuffer.address_left2 : framebuffer.address_right2); + + LOG_TRACE(Render_OpenGL, "0x{:08x} bytes from 0x{:08x}({}x{}), fmt {:x}", + framebuffer.stride * framebuffer.height, framebuffer_addr, framebuffer.width.Value(), + framebuffer.height.Value(), framebuffer.format); + + int bpp = GPU::Regs::BytesPerPixel(framebuffer.color_format); + std::size_t pixel_stride = framebuffer.stride / bpp; + + // OpenGL only supports specifying a stride in units of pixels, not bytes, unfortunately + ASSERT(pixel_stride * bpp == framebuffer.stride); + + // Ensure no bad interactions with GL_UNPACK_ALIGNMENT, which by default + // only allows rows to have a memory alignement of 4. + ASSERT(pixel_stride % 4 == 0); + + if (!Rasterizer()->AccelerateDisplay(framebuffer, framebuffer_addr, + static_cast(pixel_stride), screen_info)) { + // Reset the screen info's display texture to its own permanent texture + screen_info.display_texture = screen_info.texture.resource.handle; + screen_info.display_texcoords = Common::Rectangle(0.f, 0.f, 1.f, 1.f); + + Memory::RasterizerFlushRegion(framebuffer_addr, framebuffer.stride * framebuffer.height); + + const u8* framebuffer_data = VideoCore::g_memory->GetPhysicalPointer(framebuffer_addr); + + state.texture_units[0].texture_2d = screen_info.texture.resource.handle; + state.Apply(); + + glActiveTexture(GL_TEXTURE0); + glPixelStorei(GL_UNPACK_ROW_LENGTH, (GLint)pixel_stride); + + // Update existing texture + // TODO: Test what happens on hardware when you change the framebuffer dimensions so that + // they differ from the LCD resolution. + // TODO: Applications could theoretically crash Citra here by specifying too large + // framebuffer sizes. We should make sure that this cannot happen. + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, framebuffer.width, framebuffer.height, + screen_info.texture.gl_format, screen_info.texture.gl_type, + framebuffer_data); + + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + + state.texture_units[0].texture_2d = 0; + state.Apply(); + } +} + +/** + * Fills active OpenGL texture with the given RGB color. Since the color is solid, the texture can + * be 1x1 but will stretch across whatever it's rendered on. + */ +void RendererOpenGL::LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b, + const TextureInfo& texture) { + state.texture_units[0].texture_2d = texture.resource.handle; + state.Apply(); + + glActiveTexture(GL_TEXTURE0); + u8 framebuffer_data[3] = {color_r, color_g, color_b}; + + // Update existing texture + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, framebuffer_data); + + state.texture_units[0].texture_2d = 0; + state.Apply(); +} + +/** + * Initializes the OpenGL state and creates persistent objects. + */ +void RendererOpenGL::InitOpenGLObjects() { + glClearColor(Settings::values.bg_red, Settings::values.bg_green, Settings::values.bg_blue, + 0.0f); + + filter_sampler.Create(); + ReloadSampler(); + + ReloadShader(); + + // Generate VBO handle for drawing + vertex_buffer.Create(); + + // Generate VAO + vertex_array.Create(); + + state.draw.vertex_array = vertex_array.handle; + state.draw.vertex_buffer = vertex_buffer.handle; + state.draw.uniform_buffer = 0; + state.Apply(); + + // Attach vertex data to VAO + glBufferData(GL_ARRAY_BUFFER, sizeof(ScreenRectVertex) * 4, nullptr, GL_STREAM_DRAW); + glVertexAttribPointer(attrib_position, 2, GL_FLOAT, GL_FALSE, sizeof(ScreenRectVertex), + (GLvoid*)offsetof(ScreenRectVertex, position)); + glVertexAttribPointer(attrib_tex_coord, 2, GL_FLOAT, GL_FALSE, sizeof(ScreenRectVertex), + (GLvoid*)offsetof(ScreenRectVertex, tex_coord)); + glEnableVertexAttribArray(attrib_position); + glEnableVertexAttribArray(attrib_tex_coord); + + // Allocate textures for each screen + for (auto& screen_info : screen_infos) { + screen_info.texture.resource.Create(); + + // Allocation of storage is deferred until the first frame, when we + // know the framebuffer size. + + state.texture_units[0].texture_2d = screen_info.texture.resource.handle; + state.Apply(); + + glActiveTexture(GL_TEXTURE0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + screen_info.display_texture = screen_info.texture.resource.handle; + } + + state.texture_units[0].texture_2d = 0; + state.Apply(); +} + +void RendererOpenGL::ReloadSampler() { + glSamplerParameteri(filter_sampler.handle, GL_TEXTURE_MIN_FILTER, + Settings::values.filter_mode ? GL_LINEAR : GL_NEAREST); + glSamplerParameteri(filter_sampler.handle, GL_TEXTURE_MAG_FILTER, + Settings::values.filter_mode ? GL_LINEAR : GL_NEAREST); + glSamplerParameteri(filter_sampler.handle, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glSamplerParameteri(filter_sampler.handle, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); +} + +void RendererOpenGL::ReloadShader() { + // Link shaders and get variable locations + std::string shader_data; + if (GLES) { + shader_data += fragment_shader_precision_OES; + } + if (Settings::values.render_3d == Settings::StereoRenderOption::Anaglyph) { + if (Settings::values.pp_shader_name == "dubois (builtin)") { + shader_data += fragment_shader_anaglyph; + } else { + std::string shader_text = + OpenGL::GetPostProcessingShaderCode(true, Settings::values.pp_shader_name); + if (shader_text.empty()) { + // Should probably provide some information that the shader couldn't load + shader_data += fragment_shader_anaglyph; + } else { + shader_data += shader_text; + } + } + } else if (Settings::values.render_3d == Settings::StereoRenderOption::Interlaced || + Settings::values.render_3d == Settings::StereoRenderOption::ReverseInterlaced) { + if (Settings::values.pp_shader_name == "horizontal (builtin)") { + shader_data += fragment_shader_interlaced; + } else { + std::string shader_text = + OpenGL::GetPostProcessingShaderCode(true, Settings::values.pp_shader_name); + if (shader_text.empty()) { + // Should probably provide some information that the shader couldn't load + shader_data += fragment_shader_interlaced; + } else { + shader_data += shader_text; + } + } + } else { + if (Settings::values.pp_shader_name == "none (builtin)") { + shader_data += fragment_shader; + } else { + std::string shader_text = + OpenGL::GetPostProcessingShaderCode(false, Settings::values.pp_shader_name); + if (shader_text.empty()) { + // Should probably provide some information that the shader couldn't load + shader_data += fragment_shader; + } else { + shader_data += shader_text; + } + } + } + shader.Create(vertex_shader, shader_data.c_str()); + state.draw.shader_program = shader.handle; + state.Apply(); + uniform_modelview_matrix = glGetUniformLocation(shader.handle, "modelview_matrix"); + uniform_color_texture = glGetUniformLocation(shader.handle, "color_texture"); + if (Settings::values.render_3d == Settings::StereoRenderOption::Anaglyph || + Settings::values.render_3d == Settings::StereoRenderOption::Interlaced || + Settings::values.render_3d == Settings::StereoRenderOption::ReverseInterlaced) { + uniform_color_texture_r = glGetUniformLocation(shader.handle, "color_texture_r"); + } + if (Settings::values.render_3d == Settings::StereoRenderOption::Interlaced || + Settings::values.render_3d == Settings::StereoRenderOption::ReverseInterlaced) { + GLuint uniform_reverse_interlaced = + glGetUniformLocation(shader.handle, "reverse_interlaced"); + if (Settings::values.render_3d == Settings::StereoRenderOption::ReverseInterlaced) + glUniform1i(uniform_reverse_interlaced, 1); + else + glUniform1i(uniform_reverse_interlaced, 0); + } + uniform_i_resolution = glGetUniformLocation(shader.handle, "i_resolution"); + uniform_o_resolution = glGetUniformLocation(shader.handle, "o_resolution"); + uniform_layer = glGetUniformLocation(shader.handle, "layer"); + attrib_position = glGetAttribLocation(shader.handle, "vert_position"); + attrib_tex_coord = glGetAttribLocation(shader.handle, "vert_tex_coord"); +} + +void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture, + const GPU::Regs::FramebufferConfig& framebuffer) { + GPU::Regs::PixelFormat format = framebuffer.color_format; + GLint internal_format; + + texture.format = format; + texture.width = framebuffer.width; + texture.height = framebuffer.height; + + switch (format) { + case GPU::Regs::PixelFormat::RGBA8: + internal_format = GL_RGBA; + texture.gl_format = GL_RGBA; + texture.gl_type = GLES ? GL_UNSIGNED_BYTE : GL_UNSIGNED_INT_8_8_8_8; + break; + + case GPU::Regs::PixelFormat::RGB8: + // This pixel format uses BGR since GL_UNSIGNED_BYTE specifies byte-order, unlike every + // specific OpenGL type used in this function using native-endian (that is, little-endian + // mostly everywhere) for words or half-words. + // TODO: check how those behave on big-endian processors. + internal_format = GL_RGB; + + // GLES Dosen't support BGR , Use RGB instead + texture.gl_format = GLES ? GL_RGB : GL_BGR; + texture.gl_type = GL_UNSIGNED_BYTE; + break; + + case GPU::Regs::PixelFormat::RGB565: + internal_format = GL_RGB; + texture.gl_format = GL_RGB; + texture.gl_type = GL_UNSIGNED_SHORT_5_6_5; + break; + + case GPU::Regs::PixelFormat::RGB5A1: + internal_format = GL_RGBA; + texture.gl_format = GL_RGBA; + texture.gl_type = GL_UNSIGNED_SHORT_5_5_5_1; + break; + + case GPU::Regs::PixelFormat::RGBA4: + internal_format = GL_RGBA; + texture.gl_format = GL_RGBA; + texture.gl_type = GL_UNSIGNED_SHORT_4_4_4_4; + break; + + default: + UNIMPLEMENTED(); + } + + state.texture_units[0].texture_2d = texture.resource.handle; + state.Apply(); + + glActiveTexture(GL_TEXTURE0); + glTexImage2D(GL_TEXTURE_2D, 0, internal_format, texture.width, texture.height, 0, + texture.gl_format, texture.gl_type, nullptr); + + state.texture_units[0].texture_2d = 0; + state.Apply(); +} + +/** + * Draws a single texture to the emulator window, rotating the texture to correct for the 3DS's LCD + * rotation. + */ +void RendererOpenGL::DrawSingleScreenRotated(const ScreenInfo& screen_info, float x, float y, + float w, float h) { + const auto& texcoords = screen_info.display_texcoords; + + const std::array vertices = {{ + ScreenRectVertex(x, y, texcoords.bottom, texcoords.left), + ScreenRectVertex(x + w, y, texcoords.bottom, texcoords.right), + ScreenRectVertex(x, y + h, texcoords.top, texcoords.left), + ScreenRectVertex(x + w, y + h, texcoords.top, texcoords.right), + }}; + + // As this is the "DrawSingleScreenRotated" function, the output resolution dimensions have been + // swapped. If a non-rotated draw-screen function were to be added for book-mode games, those + // should probably be set to the standard (w, h, 1.0 / w, 1.0 / h) ordering. + const u16 scale_factor = VideoCore::GetResolutionScaleFactor(); + glUniform4f(uniform_i_resolution, static_cast(screen_info.texture.width * scale_factor), + static_cast(screen_info.texture.height * scale_factor), + 1.0f / static_cast(screen_info.texture.width * scale_factor), + 1.0f / static_cast(screen_info.texture.height * scale_factor)); + glUniform4f(uniform_o_resolution, h, w, 1.0f / h, 1.0f / w); + state.texture_units[0].texture_2d = screen_info.display_texture; + state.texture_units[0].sampler = filter_sampler.handle; + state.Apply(); + + glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices.data()); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + state.texture_units[0].texture_2d = 0; + state.texture_units[0].sampler = 0; + state.Apply(); +} + +void RendererOpenGL::DrawSingleScreen(const ScreenInfo& screen_info, float x, float y, float w, + float h) { + const auto& texcoords = screen_info.display_texcoords; + + const std::array vertices = {{ + ScreenRectVertex(x, y, texcoords.bottom, texcoords.right), + ScreenRectVertex(x + w, y, texcoords.top, texcoords.right), + ScreenRectVertex(x, y + h, texcoords.bottom, texcoords.left), + ScreenRectVertex(x + w, y + h, texcoords.top, texcoords.left), + }}; + + const u16 scale_factor = VideoCore::GetResolutionScaleFactor(); + glUniform4f(uniform_i_resolution, static_cast(screen_info.texture.width * scale_factor), + static_cast(screen_info.texture.height * scale_factor), + 1.0f / static_cast(screen_info.texture.width * scale_factor), + 1.0f / static_cast(screen_info.texture.height * scale_factor)); + glUniform4f(uniform_o_resolution, w, h, 1.0f / w, 1.0f / h); + state.texture_units[0].texture_2d = screen_info.display_texture; + state.texture_units[0].sampler = filter_sampler.handle; + state.Apply(); + + glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices.data()); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + state.texture_units[0].texture_2d = 0; + state.texture_units[0].sampler = 0; + state.Apply(); +} + +/** + * Draws a single texture to the emulator window, rotating the texture to correct for the 3DS's LCD + * rotation. + */ +void RendererOpenGL::DrawSingleScreenStereoRotated(const ScreenInfo& screen_info_l, + const ScreenInfo& screen_info_r, float x, + float y, float w, float h) { + const auto& texcoords = screen_info_l.display_texcoords; + + const std::array vertices = {{ + ScreenRectVertex(x, y, texcoords.bottom, texcoords.left), + ScreenRectVertex(x + w, y, texcoords.bottom, texcoords.right), + ScreenRectVertex(x, y + h, texcoords.top, texcoords.left), + ScreenRectVertex(x + w, y + h, texcoords.top, texcoords.right), + }}; + + const u16 scale_factor = VideoCore::GetResolutionScaleFactor(); + glUniform4f(uniform_i_resolution, + static_cast(screen_info_l.texture.width * scale_factor), + static_cast(screen_info_l.texture.height * scale_factor), + 1.0f / static_cast(screen_info_l.texture.width * scale_factor), + 1.0f / static_cast(screen_info_l.texture.height * scale_factor)); + glUniform4f(uniform_o_resolution, h, w, 1.0f / h, 1.0f / w); + state.texture_units[0].texture_2d = screen_info_l.display_texture; + state.texture_units[1].texture_2d = screen_info_r.display_texture; + state.texture_units[0].sampler = filter_sampler.handle; + state.texture_units[1].sampler = filter_sampler.handle; + state.Apply(); + + glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices.data()); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + state.texture_units[0].texture_2d = 0; + state.texture_units[1].texture_2d = 0; + state.texture_units[0].sampler = 0; + state.texture_units[1].sampler = 0; + state.Apply(); +} + +void RendererOpenGL::DrawSingleScreenStereo(const ScreenInfo& screen_info_l, + const ScreenInfo& screen_info_r, float x, float y, + float w, float h) { + const auto& texcoords = screen_info_l.display_texcoords; + + const std::array vertices = {{ + ScreenRectVertex(x, y, texcoords.bottom, texcoords.right), + ScreenRectVertex(x + w, y, texcoords.top, texcoords.right), + ScreenRectVertex(x, y + h, texcoords.bottom, texcoords.left), + ScreenRectVertex(x + w, y + h, texcoords.top, texcoords.left), + }}; + + const u16 scale_factor = VideoCore::GetResolutionScaleFactor(); + glUniform4f(uniform_i_resolution, + static_cast(screen_info_l.texture.width * scale_factor), + static_cast(screen_info_l.texture.height * scale_factor), + 1.0f / static_cast(screen_info_l.texture.width * scale_factor), + 1.0f / static_cast(screen_info_l.texture.height * scale_factor)); + glUniform4f(uniform_o_resolution, w, h, 1.0f / w, 1.0f / h); + state.texture_units[0].texture_2d = screen_info_l.display_texture; + state.texture_units[1].texture_2d = screen_info_r.display_texture; + state.texture_units[0].sampler = filter_sampler.handle; + state.texture_units[1].sampler = filter_sampler.handle; + state.Apply(); + + glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices.data()); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + state.texture_units[0].texture_2d = 0; + state.texture_units[1].texture_2d = 0; + state.texture_units[0].sampler = 0; + state.texture_units[1].sampler = 0; + state.Apply(); +} + +/** + * Draws the emulated screens to the emulator window. + */ +void RendererOpenGL::DrawScreens(const Layout::FramebufferLayout& layout, bool flipped) { + if (VideoCore::g_renderer_bg_color_update_requested.exchange(false)) { + // Update background color before drawing + glClearColor(Settings::values.bg_red, Settings::values.bg_green, Settings::values.bg_blue, + 0.0f); + } + + if (VideoCore::g_renderer_sampler_update_requested.exchange(false)) { + // Set the new filtering mode for the sampler + ReloadSampler(); + } + + if (VideoCore::g_renderer_shader_update_requested.exchange(false)) { + // Update fragment shader before drawing + shader.Release(); + // Link shaders and get variable locations + ReloadShader(); + } + + const auto& top_screen = layout.top_screen; + const auto& bottom_screen = layout.bottom_screen; + + glViewport(0, 0, layout.width, layout.height); + glClear(GL_COLOR_BUFFER_BIT); + + // Set projection matrix + std::array ortho_matrix = + MakeOrthographicMatrix((float)layout.width, (float)layout.height, flipped); + glUniformMatrix3x2fv(uniform_modelview_matrix, 1, GL_FALSE, ortho_matrix.data()); + + // Bind texture in Texture Unit 0 + glUniform1i(uniform_color_texture, 0); + + const bool stereo_single_screen = + Settings::values.render_3d == Settings::StereoRenderOption::Anaglyph || + Settings::values.render_3d == Settings::StereoRenderOption::Interlaced || + Settings::values.render_3d == Settings::StereoRenderOption::ReverseInterlaced; + + // Bind a second texture for the right eye if in Anaglyph mode + if (stereo_single_screen) { + glUniform1i(uniform_color_texture_r, 1); + } + + glUniform1i(uniform_layer, 0); + if (layout.top_screen_enabled) { + if (layout.is_rotated) { + if (Settings::values.render_3d == Settings::StereoRenderOption::Off) { + DrawSingleScreenRotated(screen_infos[0], (float)top_screen.left, + (float)top_screen.top, (float)top_screen.GetWidth(), + (float)top_screen.GetHeight()); + } else if (Settings::values.render_3d == Settings::StereoRenderOption::SideBySide) { + DrawSingleScreenRotated(screen_infos[0], (float)top_screen.left / 2, + (float)top_screen.top, (float)top_screen.GetWidth() / 2, + (float)top_screen.GetHeight()); + glUniform1i(uniform_layer, 1); + DrawSingleScreenRotated(screen_infos[1], + ((float)top_screen.left / 2) + ((float)layout.width / 2), + (float)top_screen.top, (float)top_screen.GetWidth() / 2, + (float)top_screen.GetHeight()); + } else if (Settings::values.render_3d == Settings::StereoRenderOption::CardboardVR) { + DrawSingleScreenRotated(screen_infos[0], layout.top_screen.left, + layout.top_screen.top, layout.top_screen.GetWidth(), + layout.top_screen.GetHeight()); + glUniform1i(uniform_layer, 1); + DrawSingleScreenRotated(screen_infos[1], + layout.cardboard.top_screen_right_eye + + ((float)layout.width / 2), + layout.top_screen.top, layout.top_screen.GetWidth(), + layout.top_screen.GetHeight()); + } else if (stereo_single_screen) { + DrawSingleScreenStereoRotated( + screen_infos[0], screen_infos[1], (float)top_screen.left, (float)top_screen.top, + (float)top_screen.GetWidth(), (float)top_screen.GetHeight()); + } + } else { + if (Settings::values.render_3d == Settings::StereoRenderOption::Off) { + DrawSingleScreen(screen_infos[0], (float)top_screen.left, (float)top_screen.top, + (float)top_screen.GetWidth(), (float)top_screen.GetHeight()); + } else if (Settings::values.render_3d == Settings::StereoRenderOption::SideBySide) { + DrawSingleScreen(screen_infos[0], (float)top_screen.left / 2, (float)top_screen.top, + (float)top_screen.GetWidth() / 2, (float)top_screen.GetHeight()); + glUniform1i(uniform_layer, 1); + DrawSingleScreen(screen_infos[1], + ((float)top_screen.left / 2) + ((float)layout.width / 2), + (float)top_screen.top, (float)top_screen.GetWidth() / 2, + (float)top_screen.GetHeight()); + } else if (Settings::values.render_3d == Settings::StereoRenderOption::CardboardVR) { + DrawSingleScreen(screen_infos[0], layout.top_screen.left, layout.top_screen.top, + layout.top_screen.GetWidth(), layout.top_screen.GetHeight()); + glUniform1i(uniform_layer, 1); + DrawSingleScreen(screen_infos[1], + layout.cardboard.top_screen_right_eye + ((float)layout.width / 2), + layout.top_screen.top, layout.top_screen.GetWidth(), + layout.top_screen.GetHeight()); + } else if (stereo_single_screen) { + DrawSingleScreenStereo(screen_infos[0], screen_infos[1], (float)top_screen.left, + (float)top_screen.top, (float)top_screen.GetWidth(), + (float)top_screen.GetHeight()); + } + } + } + glUniform1i(uniform_layer, 0); + if (layout.bottom_screen_enabled) { + if (layout.is_rotated) { + if (Settings::values.render_3d == Settings::StereoRenderOption::Off) { + DrawSingleScreenRotated(screen_infos[2], (float)bottom_screen.left, + (float)bottom_screen.top, (float)bottom_screen.GetWidth(), + (float)bottom_screen.GetHeight()); + } else if (Settings::values.render_3d == Settings::StereoRenderOption::SideBySide) { + DrawSingleScreenRotated( + screen_infos[2], (float)bottom_screen.left / 2, (float)bottom_screen.top, + (float)bottom_screen.GetWidth() / 2, (float)bottom_screen.GetHeight()); + glUniform1i(uniform_layer, 1); + DrawSingleScreenRotated( + screen_infos[2], ((float)bottom_screen.left / 2) + ((float)layout.width / 2), + (float)bottom_screen.top, (float)bottom_screen.GetWidth() / 2, + (float)bottom_screen.GetHeight()); + } else if (Settings::values.render_3d == Settings::StereoRenderOption::CardboardVR) { + DrawSingleScreenRotated(screen_infos[2], layout.bottom_screen.left, + layout.bottom_screen.top, layout.bottom_screen.GetWidth(), + layout.bottom_screen.GetHeight()); + glUniform1i(uniform_layer, 1); + DrawSingleScreenRotated(screen_infos[2], + layout.cardboard.bottom_screen_right_eye + + ((float)layout.width / 2), + layout.bottom_screen.top, layout.bottom_screen.GetWidth(), + layout.bottom_screen.GetHeight()); + } else if (stereo_single_screen) { + DrawSingleScreenStereoRotated(screen_infos[2], screen_infos[2], + (float)bottom_screen.left, (float)bottom_screen.top, + (float)bottom_screen.GetWidth(), + (float)bottom_screen.GetHeight()); + } + } else { + if (Settings::values.render_3d == Settings::StereoRenderOption::Off) { + DrawSingleScreen(screen_infos[2], (float)bottom_screen.left, + (float)bottom_screen.top, (float)bottom_screen.GetWidth(), + (float)bottom_screen.GetHeight()); + } else if (Settings::values.render_3d == Settings::StereoRenderOption::SideBySide) { + DrawSingleScreen(screen_infos[2], (float)bottom_screen.left / 2, + (float)bottom_screen.top, (float)bottom_screen.GetWidth() / 2, + (float)bottom_screen.GetHeight()); + glUniform1i(uniform_layer, 1); + DrawSingleScreen(screen_infos[2], + ((float)bottom_screen.left / 2) + ((float)layout.width / 2), + (float)bottom_screen.top, (float)bottom_screen.GetWidth() / 2, + (float)bottom_screen.GetHeight()); + } else if (Settings::values.render_3d == Settings::StereoRenderOption::CardboardVR) { + DrawSingleScreen(screen_infos[2], layout.bottom_screen.left, + layout.bottom_screen.top, layout.bottom_screen.GetWidth(), + layout.bottom_screen.GetHeight()); + glUniform1i(uniform_layer, 1); + DrawSingleScreen(screen_infos[2], + layout.cardboard.bottom_screen_right_eye + + ((float)layout.width / 2), + layout.bottom_screen.top, layout.bottom_screen.GetWidth(), + layout.bottom_screen.GetHeight()); + } else if (stereo_single_screen) { + DrawSingleScreenStereo(screen_infos[2], screen_infos[2], (float)bottom_screen.left, + (float)bottom_screen.top, (float)bottom_screen.GetWidth(), + (float)bottom_screen.GetHeight()); + } + } + } +} + +void RendererOpenGL::TryPresent(int timeout_ms) { + const auto& layout = render_window.GetFramebufferLayout(); + auto frame = render_window.mailbox->TryGetPresentFrame(timeout_ms); + if (!frame) { + LOG_DEBUG(Render_OpenGL, "TryGetPresentFrame returned no frame to present"); + return; + } + + // Clearing before a full overwrite of a fbo can signal to drivers that they can avoid a + // readback since we won't be doing any blending + glClear(GL_COLOR_BUFFER_BIT); + + // Recreate the presentation FBO if the color attachment was changed + if (frame->color_reloaded) { + LOG_DEBUG(Render_OpenGL, "Reloading present frame"); + render_window.mailbox->ReloadPresentFrame(frame, layout.width, layout.height); + } + glWaitSync(frame->render_fence, 0, GL_TIMEOUT_IGNORED); + // INTEL workaround. + // Normally we could just delete the draw fence here, but due to driver bugs, we can just delete + // it on the emulation thread without too much penalty + // glDeleteSync(frame.render_sync); + // frame.render_sync = 0; + + glBindFramebuffer(GL_READ_FRAMEBUFFER, frame->present.handle); + glBlitFramebuffer(0, 0, frame->width, frame->height, 0, 0, layout.width, layout.height, + GL_COLOR_BUFFER_BIT, GL_LINEAR); + + // Delete the fence if we're re-presenting to avoid leaking fences + if (frame->present_fence) { + glDeleteSync(frame->present_fence); + } + + /* insert fence for the main thread to block on */ + frame->present_fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + glFlush(); + + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); +} + +/// Updates the framerate +void RendererOpenGL::UpdateFramerate() {} + +void RendererOpenGL::PrepareVideoDumping() { + auto* mailbox = static_cast(frame_dumper.mailbox.get()); + { + std::unique_lock lock(mailbox->swap_chain_lock); + mailbox->quit = false; + } + frame_dumper.StartDumping(); +} + +void RendererOpenGL::CleanupVideoDumping() { + frame_dumper.StopDumping(); + auto* mailbox = static_cast(frame_dumper.mailbox.get()); + { + std::unique_lock lock(mailbox->swap_chain_lock); + mailbox->quit = true; + } + mailbox->free_cv.notify_one(); +} + +static const char* GetSource(GLenum source) { +#define RET(s) \ + case GL_DEBUG_SOURCE_##s: \ + return #s + switch (source) { + RET(API); + RET(WINDOW_SYSTEM); + RET(SHADER_COMPILER); + RET(THIRD_PARTY); + RET(APPLICATION); + RET(OTHER); + default: + UNREACHABLE(); + } +#undef RET +} + +static const char* GetType(GLenum type) { +#define RET(t) \ + case GL_DEBUG_TYPE_##t: \ + return #t + switch (type) { + RET(ERROR); + RET(DEPRECATED_BEHAVIOR); + RET(UNDEFINED_BEHAVIOR); + RET(PORTABILITY); + RET(PERFORMANCE); + RET(OTHER); + RET(MARKER); + default: + UNREACHABLE(); + } +#undef RET +} + +static void APIENTRY DebugHandler(GLenum source, GLenum type, GLuint id, GLenum severity, + GLsizei length, const GLchar* message, const void* user_param) { + Log::Level level; + switch (severity) { + case GL_DEBUG_SEVERITY_HIGH: + level = Log::Level::Critical; + break; + case GL_DEBUG_SEVERITY_MEDIUM: + level = Log::Level::Warning; + break; + case GL_DEBUG_SEVERITY_NOTIFICATION: + case GL_DEBUG_SEVERITY_LOW: + level = Log::Level::Debug; + break; + } + LOG_GENERIC(Log::Class::Render_OpenGL, level, "{} {} {}: {}", GetSource(source), GetType(type), + id, message); +} + +/// Initialize the renderer +VideoCore::ResultStatus RendererOpenGL::Init() { +#ifndef ANDROID + if (!gladLoadGL()) { + return VideoCore::ResultStatus::ErrorBelowGL33; + } + + // Qualcomm has some spammy info messages that are marked as errors but not important + // https://developer.qualcomm.com/comment/11845 + if (GLAD_GL_KHR_debug) { + glEnable(GL_DEBUG_OUTPUT); + glDebugMessageCallback(DebugHandler, nullptr); + } +#endif + + const char* gl_version{reinterpret_cast(glGetString(GL_VERSION))}; + const char* gpu_vendor{reinterpret_cast(glGetString(GL_VENDOR))}; + const char* gpu_model{reinterpret_cast(glGetString(GL_RENDERER))}; + + LOG_INFO(Render_OpenGL, "GL_VERSION: {}", gl_version); + LOG_INFO(Render_OpenGL, "GL_VENDOR: {}", gpu_vendor); + LOG_INFO(Render_OpenGL, "GL_RENDERER: {}", gpu_model); + + auto& telemetry_session = Core::System::GetInstance().TelemetrySession(); + constexpr auto user_system = Common::Telemetry::FieldType::UserSystem; + telemetry_session.AddField(user_system, "GPU_Vendor", std::string(gpu_vendor)); + telemetry_session.AddField(user_system, "GPU_Model", std::string(gpu_model)); + telemetry_session.AddField(user_system, "GPU_OpenGL_Version", std::string(gl_version)); + + if (!strcmp(gpu_vendor, "GDI Generic")) { + return VideoCore::ResultStatus::ErrorGenericDrivers; + } + + if (!(GLAD_GL_VERSION_3_3 || GLAD_GL_ES_VERSION_3_1)) { + return VideoCore::ResultStatus::ErrorBelowGL33; + } + + InitOpenGLObjects(); + + RefreshRasterizerSetting(); + + return VideoCore::ResultStatus::Success; +} + +/// Shutdown the renderer +void RendererOpenGL::ShutDown() {} + +} // namespace OpenGL diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h new file mode 100644 index 000000000..8ef99ef4a --- /dev/null +++ b/src/video_core/renderer_vulkan/renderer_vulkan.h @@ -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 +#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 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& 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 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 diff --git a/src/video_core/renderer_vulkan/vk_buffer.cpp b/src/video_core/renderer_vulkan/vk_buffer.cpp new file mode 100644 index 000000000..a15d4310d --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_buffer.cpp @@ -0,0 +1,113 @@ +#include "vk_buffer.h" +#include "vk_context.h" +#include +#include +#include +#include + +Buffer::Buffer(std::shared_ptr 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 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& 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); +} diff --git a/src/video_core/renderer_vulkan/vk_buffer.h b/src/video_core/renderer_vulkan/vk_buffer.h new file mode 100644 index 000000000..cd82a251e --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_buffer.h @@ -0,0 +1,71 @@ +#pragma once +#include +#include +#include +#include +#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 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 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 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 context; +}; + +class VertexBuffer +{ +public: + VertexBuffer(const std::shared_ptr& 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 context; +}; diff --git a/src/video_core/renderer_vulkan/vk_context.cpp b/src/video_core/renderer_vulkan/vk_context.cpp new file mode 100644 index 000000000..13a7647ca --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_context.cpp @@ -0,0 +1,304 @@ +#include "vk_context.h" +#include "vk_buffer.h" +#include "vk_swapchain.h" +#include "vk_texture.h" +#include +#include + +PipelineLayoutInfo::PipelineLayoutInfo(const std::shared_ptr& 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 buffer(size); + + shaderfile.seekg(0); + shaderfile.read(buffer.data(), size); + shaderfile.close(); + + auto module = context->device->createShaderModule({ {}, buffer.size(), reinterpret_cast(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 device_extensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME }; + + auto queue_info = vk::DeviceQueueCreateInfo({}, queue_family, 1, &default_queue_priority); + + std::array 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 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 image_infos; + std::array buffer_infos; + + std::vector 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(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(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); +} diff --git a/src/video_core/renderer_vulkan/vk_context.h b/src/video_core/renderer_vulkan/vk_context.h new file mode 100644 index 000000000..1e4d576bd --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_context.h @@ -0,0 +1,75 @@ +#pragma once +#include "vk_swapchain.h" +#include +#include + +class VkWindow; +class VkContext; + +constexpr int MAX_BINDING_COUNT = 10; + +struct PipelineLayoutInfo +{ + friend class VkContext; + PipelineLayoutInfo(const std::shared_ptr& 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::vector>; + + std::shared_ptr context; + std::unordered_map resource_types; + std::unordered_map needed; + std::vector 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, MAX_FRAMES_IN_FLIGHT> descriptor_layouts; + std::array, MAX_FRAMES_IN_FLIGHT> descriptor_sets; + + // Command buffer + vk::UniqueCommandPool command_pool; + std::vector command_buffers; + + // Window + VkWindow* window; + SwapchainInfo swapchain_info; +}; diff --git a/src/video_core/renderer_vulkan/vk_resource_manager.cpp b/src/video_core/renderer_vulkan/vk_resource_manager.cpp new file mode 100644 index 000000000..f185b2355 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_resource_manager.cpp @@ -0,0 +1,246 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#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& 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 diff --git a/src/video_core/renderer_vulkan/vk_resource_manager.h b/src/video_core/renderer_vulkan/vk_resource_manager.h new file mode 100644 index 000000000..4cbadf46c --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_resource_manager.h @@ -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 +#include +#include +#include +#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 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 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 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 context; +}; + +class VKTexture : public NonCopyable, public Resource +{ + friend class VkContext; +public: + VKTexture(const std::shared_ptr& 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 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& 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(o.handle, 0); + } + ~OGLPipeline() { + Release(); + } + OGLPipeline& operator=(OGLPipeline&& 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 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 diff --git a/src/video_core/renderer_vulkan/vk_state.cpp b/src/video_core/renderer_vulkan/vk_state.cpp new file mode 100644 index 000000000..89944f80e --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_state.cpp @@ -0,0 +1,452 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#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(i)); + } else { + glDisable(GL_CLIP_DISTANCE0 + static_cast(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 diff --git a/src/video_core/renderer_vulkan/vk_state.h b/src/video_core/renderer_vulkan/vk_state.h new file mode 100644 index 000000000..5c8d85b66 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_state.h @@ -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 +#include + +namespace Vulkan { + +namespace TextureUnits { + +struct TextureUnit { + GLint id; + constexpr GLenum Enum() const { + return static_cast(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 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 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 diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp new file mode 100644 index 000000000..76b5ade66 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp @@ -0,0 +1,435 @@ +#include "vk_swapchain.h" +#include "vk_context.h" +#include "vk_buffer.h" +#include + +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(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{ 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 wait_stages = { vk::PipelineStageFlagBits::eColorAttachmentOutput }; + std::array 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 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 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(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::max()) + { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + vk::Extent2D extent = { static_cast(width), static_cast(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 }); + } +} diff --git a/src/video_core/renderer_vulkan/vk_swapchain.h b/src/video_core/renderer_vulkan/vk_swapchain.h new file mode 100644 index 000000000..ffaa00455 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_swapchain.h @@ -0,0 +1,100 @@ +#pragma once +#include +#include +#include + +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 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 context; + vk::Queue present_queue; + + // Swapchain objects + vk::UniqueSurfaceKHR surface; + vk::UniqueSwapchainKHR swapchain; + std::vector 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 image_semaphores; + std::vector render_semaphores; + std::vector flight_fences; +}; diff --git a/src/video_core/renderer_vulkan/vk_texture.cpp b/src/video_core/renderer_vulkan/vk_texture.cpp new file mode 100644 index 000000000..9399bc8cf --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_texture.cpp @@ -0,0 +1,147 @@ +#include "vk_texture.h" +#include "vk_buffer.h" +#include "vk_context.h" + +VkTexture::VkTexture(const std::shared_ptr& 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 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 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); +} diff --git a/src/video_core/renderer_vulkan/vk_texture.h b/src/video_core/renderer_vulkan/vk_texture.h new file mode 100644 index 000000000..cabfe27f8 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_texture.h @@ -0,0 +1,34 @@ +#pragma once +#include "vk_buffer.h" +#include +#include + +class VkContext; + +class VkTexture : public NonCopyable, public Resource +{ + friend class VkContext; +public: + VkTexture(const std::shared_ptr& context); + ~VkTexture() = default; + + void create(int width, int height, vk::ImageType type, vk::Format format = vk::Format::eR8G8B8A8Uint); + void copy_pixels(std::span pixels); + +private: + void transition_layout(vk::ImageLayout old_layout, vk::ImageLayout new_layout); + +private: + // Texture buffer + void* pixels = nullptr; + std::shared_ptr 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; +};