mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2025-01-17 20:18:28 +00:00
Render without rendering (#2152)
Some checks are pending
Build and Release / reuse (push) Waiting to run
Build and Release / clang-format (push) Waiting to run
Build and Release / get-info (push) Waiting to run
Build and Release / windows-sdl (push) Blocked by required conditions
Build and Release / windows-qt (push) Blocked by required conditions
Build and Release / macos-sdl (push) Blocked by required conditions
Build and Release / macos-qt (push) Blocked by required conditions
Build and Release / linux-sdl (push) Blocked by required conditions
Build and Release / linux-qt (push) Blocked by required conditions
Build and Release / linux-sdl-gcc (push) Blocked by required conditions
Build and Release / linux-qt-gcc (push) Blocked by required conditions
Build and Release / pre-release (push) Blocked by required conditions
Some checks are pending
Build and Release / reuse (push) Waiting to run
Build and Release / clang-format (push) Waiting to run
Build and Release / get-info (push) Waiting to run
Build and Release / windows-sdl (push) Blocked by required conditions
Build and Release / windows-qt (push) Blocked by required conditions
Build and Release / macos-sdl (push) Blocked by required conditions
Build and Release / macos-qt (push) Blocked by required conditions
Build and Release / linux-sdl (push) Blocked by required conditions
Build and Release / linux-qt (push) Blocked by required conditions
Build and Release / linux-sdl-gcc (push) Blocked by required conditions
Build and Release / linux-qt-gcc (push) Blocked by required conditions
Build and Release / pre-release (push) Blocked by required conditions
* presenter: render the game inside a ImGui window * presenter: render the previous frame to keep the render rendering * swapchain: fix swapchain image view format not being converted to unorm * devtools: fix frame graph timing
This commit is contained in:
parent
440a693fae
commit
56a6c95730
|
@ -131,6 +131,8 @@ class DebugStateImpl {
|
||||||
friend class Core::Devtools::Widget::FrameGraph;
|
friend class Core::Devtools::Widget::FrameGraph;
|
||||||
friend class Core::Devtools::Widget::ShaderList;
|
friend class Core::Devtools::Widget::ShaderList;
|
||||||
|
|
||||||
|
bool showing_debug_menu_bar = false;
|
||||||
|
|
||||||
std::queue<std::string> debug_message_popup;
|
std::queue<std::string> debug_message_popup;
|
||||||
|
|
||||||
std::mutex guest_threads_mutex{};
|
std::mutex guest_threads_mutex{};
|
||||||
|
@ -153,6 +155,9 @@ class DebugStateImpl {
|
||||||
std::vector<ShaderDump> shader_dump_list{};
|
std::vector<ShaderDump> shader_dump_list{};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
float Framerate = 1.0f / 60.0f;
|
||||||
|
float FrameDeltaTime;
|
||||||
|
|
||||||
void ShowDebugMessage(std::string message) {
|
void ShowDebugMessage(std::string message) {
|
||||||
if (message.empty()) {
|
if (message.empty()) {
|
||||||
return;
|
return;
|
||||||
|
@ -160,6 +165,10 @@ public:
|
||||||
debug_message_popup.push(std::move(message));
|
debug_message_popup.push(std::move(message));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool& ShowingDebugMenuBar() {
|
||||||
|
return showing_debug_menu_bar;
|
||||||
|
}
|
||||||
|
|
||||||
void AddCurrentThreadToGuestList();
|
void AddCurrentThreadToGuestList();
|
||||||
|
|
||||||
void RemoveCurrentThreadFromGuestList();
|
void RemoveCurrentThreadFromGuestList();
|
||||||
|
|
|
@ -28,7 +28,6 @@ static bool show_simple_fps = false;
|
||||||
static bool visibility_toggled = false;
|
static bool visibility_toggled = false;
|
||||||
|
|
||||||
static float fps_scale = 1.0f;
|
static float fps_scale = 1.0f;
|
||||||
static bool show_advanced_debug = false;
|
|
||||||
static int dump_frame_count = 1;
|
static int dump_frame_count = 1;
|
||||||
|
|
||||||
static Widget::FrameGraph frame_graph;
|
static Widget::FrameGraph frame_graph;
|
||||||
|
@ -253,8 +252,8 @@ void L::DrawAdvanced() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void L::DrawSimple() {
|
void L::DrawSimple() {
|
||||||
const auto io = GetIO();
|
const float frameRate = DebugState.Framerate;
|
||||||
Text("%.1f FPS (%.2f ms)", io.Framerate, 1000.0f / io.Framerate);
|
Text("%d FPS (%.1f ms)", static_cast<int>(std::round(1.0f / frameRate)), frameRate * 1000.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void LoadSettings(const char* line) {
|
static void LoadSettings(const char* line) {
|
||||||
|
@ -265,7 +264,7 @@ static void LoadSettings(const char* line) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (sscanf(line, "show_advanced_debug=%d", &i) == 1) {
|
if (sscanf(line, "show_advanced_debug=%d", &i) == 1) {
|
||||||
show_advanced_debug = i != 0;
|
DebugState.ShowingDebugMenuBar() = i != 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (sscanf(line, "show_frame_graph=%d", &i) == 1) {
|
if (sscanf(line, "show_frame_graph=%d", &i) == 1) {
|
||||||
|
@ -310,7 +309,7 @@ void L::SetupSettings() {
|
||||||
handler.WriteAllFn = [](ImGuiContext*, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf) {
|
handler.WriteAllFn = [](ImGuiContext*, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf) {
|
||||||
buf->appendf("[%s][Data]\n", handler->TypeName);
|
buf->appendf("[%s][Data]\n", handler->TypeName);
|
||||||
buf->appendf("fps_scale=%f\n", fps_scale);
|
buf->appendf("fps_scale=%f\n", fps_scale);
|
||||||
buf->appendf("show_advanced_debug=%d\n", show_advanced_debug);
|
buf->appendf("show_advanced_debug=%d\n", DebugState.ShowingDebugMenuBar());
|
||||||
buf->appendf("show_frame_graph=%d\n", frame_graph.is_open);
|
buf->appendf("show_frame_graph=%d\n", frame_graph.is_open);
|
||||||
buf->appendf("dump_frame_count=%d\n", dump_frame_count);
|
buf->appendf("dump_frame_count=%d\n", dump_frame_count);
|
||||||
buf->append("\n");
|
buf->append("\n");
|
||||||
|
@ -336,12 +335,12 @@ void L::Draw() {
|
||||||
|
|
||||||
if (!DebugState.IsGuestThreadsPaused()) {
|
if (!DebugState.IsGuestThreadsPaused()) {
|
||||||
const auto fn = DebugState.flip_frame_count.load();
|
const auto fn = DebugState.flip_frame_count.load();
|
||||||
frame_graph.AddFrame(fn, io.DeltaTime);
|
frame_graph.AddFrame(fn, DebugState.FrameDeltaTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsKeyPressed(ImGuiKey_F10, false)) {
|
if (IsKeyPressed(ImGuiKey_F10, false)) {
|
||||||
if (io.KeyCtrl) {
|
if (io.KeyCtrl) {
|
||||||
show_advanced_debug = !show_advanced_debug;
|
DebugState.ShowingDebugMenuBar() ^= true;
|
||||||
} else {
|
} else {
|
||||||
show_simple_fps = !show_simple_fps;
|
show_simple_fps = !show_simple_fps;
|
||||||
}
|
}
|
||||||
|
@ -376,7 +375,7 @@ void L::Draw() {
|
||||||
End();
|
End();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (show_advanced_debug) {
|
if (DebugState.ShowingDebugMenuBar()) {
|
||||||
PushFont(io.Fonts->Fonts[IMGUI_FONT_MONO]);
|
PushFont(io.Fonts->Fonts[IMGUI_FONT_MONO]);
|
||||||
PushID("DevtoolsLayer");
|
PushID("DevtoolsLayer");
|
||||||
DrawAdvanced();
|
DrawAdvanced();
|
||||||
|
|
|
@ -83,15 +83,13 @@ void FrameGraph::Draw() {
|
||||||
|
|
||||||
auto isSystemPaused = DebugState.IsGuestThreadsPaused();
|
auto isSystemPaused = DebugState.IsGuestThreadsPaused();
|
||||||
|
|
||||||
static float deltaTime;
|
|
||||||
static float frameRate;
|
|
||||||
|
|
||||||
if (!isSystemPaused) {
|
if (!isSystemPaused) {
|
||||||
deltaTime = io.DeltaTime * 1000.0f;
|
deltaTime = DebugState.FrameDeltaTime * 1000.0f;
|
||||||
frameRate = 1000.0f / deltaTime;
|
frameRate = 1000.0f / deltaTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
Text("Frame time: %.3f ms (%.1f FPS)", deltaTime, frameRate);
|
Text("Frame time: %.3f ms (%.1f FPS)", deltaTime, frameRate);
|
||||||
|
Text("Presenter time: %.3f ms (%.1f FPS)", io.DeltaTime * 1000.0f, 1.0f / io.DeltaTime);
|
||||||
Text("Flip frame: %d Gnm submit frame: %d", DebugState.flip_frame_count.load(),
|
Text("Flip frame: %d Gnm submit frame: %d", DebugState.flip_frame_count.load(),
|
||||||
DebugState.gnm_frame_count.load());
|
DebugState.gnm_frame_count.load());
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,9 @@ class FrameGraph {
|
||||||
|
|
||||||
std::array<FrameInfo, FRAME_BUFFER_SIZE> frame_list{};
|
std::array<FrameInfo, FRAME_BUFFER_SIZE> frame_list{};
|
||||||
|
|
||||||
|
float deltaTime{};
|
||||||
|
float frameRate{};
|
||||||
|
|
||||||
void DrawFrameGraph();
|
void DrawFrameGraph();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include <imgui.h>
|
|
||||||
|
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/config.h"
|
#include "common/config.h"
|
||||||
#include "common/debug.h"
|
#include "common/debug.h"
|
||||||
|
@ -207,6 +205,13 @@ void VideoOutDriver::DrawBlankFrame() {
|
||||||
presenter->Present(empty_frame);
|
presenter->Present(empty_frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void VideoOutDriver::DrawLastFrame() {
|
||||||
|
const auto frame = presenter->PrepareLastFrame();
|
||||||
|
if (frame != nullptr) {
|
||||||
|
presenter->Present(frame, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool VideoOutDriver::SubmitFlip(VideoOutPort* port, s32 index, s64 flip_arg,
|
bool VideoOutDriver::SubmitFlip(VideoOutPort* port, s32 index, s64 flip_arg,
|
||||||
bool is_eop /*= false*/) {
|
bool is_eop /*= false*/) {
|
||||||
{
|
{
|
||||||
|
@ -278,17 +283,24 @@ void VideoOutDriver::PresentThread(std::stop_token token) {
|
||||||
return {};
|
return {};
|
||||||
};
|
};
|
||||||
|
|
||||||
auto delay = std::chrono::microseconds{0};
|
|
||||||
while (!token.stop_requested()) {
|
while (!token.stop_requested()) {
|
||||||
timer.Start();
|
timer.Start();
|
||||||
|
|
||||||
|
if (DebugState.IsGuestThreadsPaused()) {
|
||||||
|
DrawLastFrame();
|
||||||
|
timer.End();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Check if it's time to take a request.
|
// Check if it's time to take a request.
|
||||||
auto& vblank_status = main_port.vblank_status;
|
auto& vblank_status = main_port.vblank_status;
|
||||||
if (vblank_status.count % (main_port.flip_rate + 1) == 0) {
|
if (vblank_status.count % (main_port.flip_rate + 1) == 0) {
|
||||||
const auto request = receive_request();
|
const auto request = receive_request();
|
||||||
if (!request) {
|
if (!request) {
|
||||||
if (!main_port.is_open || DebugState.IsGuestThreadsPaused()) {
|
if (!main_port.is_open) {
|
||||||
DrawBlankFrame();
|
DrawBlankFrame();
|
||||||
|
} else {
|
||||||
|
DrawLastFrame();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Flip(request);
|
Flip(request);
|
||||||
|
|
|
@ -102,7 +102,8 @@ private:
|
||||||
};
|
};
|
||||||
|
|
||||||
void Flip(const Request& req);
|
void Flip(const Request& req);
|
||||||
void DrawBlankFrame(); // Used when there is no flip request to keep ImGui up to date
|
void DrawBlankFrame(); // Video port out not open
|
||||||
|
void DrawLastFrame(); // Used when there is no flip request
|
||||||
void SubmitFlipInternal(VideoOutPort* port, s32 index, s64 flip_arg, bool is_eop = false);
|
void SubmitFlipInternal(VideoOutPort* port, s32 index, s64 flip_arg, bool is_eop = false);
|
||||||
void PresentThread(std::stop_token token);
|
void PresentThread(std::stop_token token);
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,12 @@ extern void assert_fail_debug_msg(const char* msg);
|
||||||
#define IM_VEC4_CLASS_EXTRA \
|
#define IM_VEC4_CLASS_EXTRA \
|
||||||
constexpr ImVec4(float _v) : x(_v), y(_v), z(_v), w(_v) {}
|
constexpr ImVec4(float _v) : x(_v), y(_v), z(_v), w(_v) {}
|
||||||
|
|
||||||
|
namespace ImGui {
|
||||||
|
struct Texture;
|
||||||
|
}
|
||||||
|
#define ImTextureID ImTextureID
|
||||||
|
using ImTextureID = ::ImGui::Texture*;
|
||||||
|
|
||||||
#ifdef IMGUI_USE_WCHAR32
|
#ifdef IMGUI_USE_WCHAR32
|
||||||
#error "This project uses 16 bits wchar standard like Orbis"
|
#error "This project uses 16 bits wchar standard like Orbis"
|
||||||
#endif
|
#endif
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
#include "common/config.h"
|
#include "common/config.h"
|
||||||
#include "common/path_util.h"
|
#include "common/path_util.h"
|
||||||
|
#include "core/debug_state.h"
|
||||||
#include "core/devtools/layer.h"
|
#include "core/devtools/layer.h"
|
||||||
#include "imgui/imgui_layer.h"
|
#include "imgui/imgui_layer.h"
|
||||||
#include "imgui_core.h"
|
#include "imgui_core.h"
|
||||||
|
@ -167,7 +168,7 @@ bool ProcessEvent(SDL_Event* event) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void NewFrame() {
|
ImGuiID NewFrame(bool is_reusing_frame) {
|
||||||
{
|
{
|
||||||
std::scoped_lock lock{change_layers_mutex};
|
std::scoped_lock lock{change_layers_mutex};
|
||||||
while (!change_layers.empty()) {
|
while (!change_layers.empty()) {
|
||||||
|
@ -182,17 +183,24 @@ void NewFrame() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Sdl::NewFrame();
|
Sdl::NewFrame(is_reusing_frame);
|
||||||
ImGui::NewFrame();
|
ImGui::NewFrame();
|
||||||
|
|
||||||
DockSpaceOverViewport(0, GetMainViewport(), ImGuiDockNodeFlags_PassthruCentralNode);
|
ImGuiWindowFlags flags = ImGuiDockNodeFlags_PassthruCentralNode;
|
||||||
|
if (!DebugState.ShowingDebugMenuBar()) {
|
||||||
|
flags |= ImGuiDockNodeFlags_NoTabBar;
|
||||||
|
}
|
||||||
|
ImGuiID dockId = DockSpaceOverViewport(0, GetMainViewport(), flags);
|
||||||
|
|
||||||
for (auto* layer : layers) {
|
for (auto* layer : layers) {
|
||||||
layer->Draw();
|
layer->Draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return dockId;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Render(const vk::CommandBuffer& cmdbuf, ::Vulkan::Frame* frame) {
|
void Render(const vk::CommandBuffer& cmdbuf, const vk::ImageView& image_view,
|
||||||
|
const vk::Extent2D& extent) {
|
||||||
ImGui::Render();
|
ImGui::Render();
|
||||||
ImDrawData* draw_data = GetDrawData();
|
ImDrawData* draw_data = GetDrawData();
|
||||||
if (draw_data->CmdListsCount == 0) {
|
if (draw_data->CmdListsCount == 0) {
|
||||||
|
@ -207,16 +215,16 @@ void Render(const vk::CommandBuffer& cmdbuf, ::Vulkan::Frame* frame) {
|
||||||
|
|
||||||
vk::RenderingAttachmentInfo color_attachments[1]{
|
vk::RenderingAttachmentInfo color_attachments[1]{
|
||||||
{
|
{
|
||||||
.imageView = frame->image_view,
|
.imageView = image_view,
|
||||||
.imageLayout = vk::ImageLayout::eColorAttachmentOptimal,
|
.imageLayout = vk::ImageLayout::eColorAttachmentOptimal,
|
||||||
.loadOp = vk::AttachmentLoadOp::eLoad,
|
.loadOp = vk::AttachmentLoadOp::eClear,
|
||||||
.storeOp = vk::AttachmentStoreOp::eStore,
|
.storeOp = vk::AttachmentStoreOp::eStore,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
vk::RenderingInfo render_info{};
|
vk::RenderingInfo render_info{};
|
||||||
render_info.renderArea = vk::Rect2D{
|
render_info.renderArea = vk::Rect2D{
|
||||||
.offset = {0, 0},
|
.offset = {0, 0},
|
||||||
.extent = {frame->width, frame->height},
|
.extent = extent,
|
||||||
};
|
};
|
||||||
render_info.layerCount = 1;
|
render_info.layerCount = 1;
|
||||||
render_info.colorAttachmentCount = 1;
|
render_info.colorAttachmentCount = 1;
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <imgui.h>
|
||||||
|
|
||||||
#include "video_core/renderer_vulkan/vk_instance.h"
|
#include "video_core/renderer_vulkan/vk_instance.h"
|
||||||
#include "vulkan/vulkan_handles.hpp"
|
#include "vulkan/vulkan_handles.hpp"
|
||||||
|
|
||||||
|
@ -24,8 +26,9 @@ void Shutdown(const vk::Device& device);
|
||||||
|
|
||||||
bool ProcessEvent(SDL_Event* event);
|
bool ProcessEvent(SDL_Event* event);
|
||||||
|
|
||||||
void NewFrame();
|
ImGuiID NewFrame(bool is_reusing_frame = false);
|
||||||
|
|
||||||
void Render(const vk::CommandBuffer& cmdbuf, Vulkan::Frame* frame);
|
void Render(const vk::CommandBuffer& cmdbuf, const vk::ImageView& image_view,
|
||||||
|
const vk::Extent2D& extent);
|
||||||
|
|
||||||
} // namespace ImGui::Core
|
} // namespace ImGui::Core
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
#include "common/config.h"
|
#include "common/config.h"
|
||||||
|
#include "core/debug_state.h"
|
||||||
#include "imgui_impl_sdl3.h"
|
#include "imgui_impl_sdl3.h"
|
||||||
|
|
||||||
// SDL
|
// SDL
|
||||||
|
@ -26,6 +27,7 @@ struct SdlData {
|
||||||
SDL_Window* window{};
|
SDL_Window* window{};
|
||||||
SDL_WindowID window_id{};
|
SDL_WindowID window_id{};
|
||||||
Uint64 time{};
|
Uint64 time{};
|
||||||
|
Uint64 nonReusedtime{};
|
||||||
const char* clipboard_text_data{};
|
const char* clipboard_text_data{};
|
||||||
|
|
||||||
// IME handling
|
// IME handling
|
||||||
|
@ -785,7 +787,7 @@ static void UpdateGamepads() {
|
||||||
+thumb_dead_zone, +32767);
|
+thumb_dead_zone, +32767);
|
||||||
}
|
}
|
||||||
|
|
||||||
void NewFrame() {
|
void NewFrame(bool is_reusing_frame) {
|
||||||
SdlData* bd = GetBackendData();
|
SdlData* bd = GetBackendData();
|
||||||
IM_ASSERT(bd != nullptr && "No platform backend to shutdown, or already shutdown?");
|
IM_ASSERT(bd != nullptr && "No platform backend to shutdown, or already shutdown?");
|
||||||
ImGuiIO& io = ImGui::GetIO();
|
ImGuiIO& io = ImGui::GetIO();
|
||||||
|
@ -798,9 +800,26 @@ void NewFrame() {
|
||||||
if (current_time <= bd->time)
|
if (current_time <= bd->time)
|
||||||
current_time = bd->time + 1;
|
current_time = bd->time + 1;
|
||||||
io.DeltaTime = bd->time > 0 ? (float)((double)(current_time - bd->time) / (double)frequency)
|
io.DeltaTime = bd->time > 0 ? (float)((double)(current_time - bd->time) / (double)frequency)
|
||||||
: (float)(1.0f / 60.0f);
|
: 1.0f / 60.0f;
|
||||||
bd->time = current_time;
|
bd->time = current_time;
|
||||||
|
|
||||||
|
if (!is_reusing_frame) {
|
||||||
|
if (current_time <= bd->nonReusedtime)
|
||||||
|
current_time = bd->nonReusedtime + 1;
|
||||||
|
float deltaTime =
|
||||||
|
bd->nonReusedtime > 0
|
||||||
|
? (float)((double)(current_time - bd->nonReusedtime) / (double)frequency)
|
||||||
|
: 1.0f / 60.0f;
|
||||||
|
bd->nonReusedtime = current_time;
|
||||||
|
DebugState.FrameDeltaTime = deltaTime;
|
||||||
|
float distribution = 0.016f / deltaTime / 10.0f;
|
||||||
|
if (distribution > 1.0f) {
|
||||||
|
distribution = 1.0f;
|
||||||
|
}
|
||||||
|
DebugState.Framerate =
|
||||||
|
deltaTime * distribution + DebugState.Framerate * (1.0f - distribution);
|
||||||
|
}
|
||||||
|
|
||||||
if (bd->mouse_pending_leave_frame && bd->mouse_pending_leave_frame >= ImGui::GetFrameCount() &&
|
if (bd->mouse_pending_leave_frame && bd->mouse_pending_leave_frame >= ImGui::GetFrameCount() &&
|
||||||
bd->mouse_buttons_down == 0) {
|
bd->mouse_buttons_down == 0) {
|
||||||
bd->mouse_window_id = 0;
|
bd->mouse_window_id = 0;
|
||||||
|
|
|
@ -14,7 +14,7 @@ namespace ImGui::Sdl {
|
||||||
|
|
||||||
bool Init(SDL_Window* window);
|
bool Init(SDL_Window* window);
|
||||||
void Shutdown();
|
void Shutdown();
|
||||||
void NewFrame();
|
void NewFrame(bool is_reusing);
|
||||||
bool ProcessEvent(const SDL_Event* event);
|
bool ProcessEvent(const SDL_Event* event);
|
||||||
void OnResize();
|
void OnResize();
|
||||||
|
|
||||||
|
|
|
@ -57,11 +57,12 @@ struct VkData {
|
||||||
vk::DeviceMemory font_memory{};
|
vk::DeviceMemory font_memory{};
|
||||||
vk::Image font_image{};
|
vk::Image font_image{};
|
||||||
vk::ImageView font_view{};
|
vk::ImageView font_view{};
|
||||||
vk::DescriptorSet font_descriptor_set{};
|
ImTextureID font_texture{};
|
||||||
vk::CommandBuffer font_command_buffer{};
|
vk::CommandBuffer font_command_buffer{};
|
||||||
|
|
||||||
// Render buffers
|
// Render buffers
|
||||||
WindowRenderBuffers render_buffers{};
|
WindowRenderBuffers render_buffers{};
|
||||||
|
bool enabled_blending{true};
|
||||||
|
|
||||||
VkData(const InitInfo init_info) : init_info(init_info) {
|
VkData(const InitInfo init_info) : init_info(init_info) {
|
||||||
render_buffers.count = init_info.image_count;
|
render_buffers.count = init_info.image_count;
|
||||||
|
@ -252,8 +253,8 @@ void UploadTextureData::Destroy() {
|
||||||
const InitInfo& v = bd->init_info;
|
const InitInfo& v = bd->init_info;
|
||||||
|
|
||||||
CheckVkErr(v.device.waitIdle());
|
CheckVkErr(v.device.waitIdle());
|
||||||
RemoveTexture(descriptor_set);
|
RemoveTexture(im_texture);
|
||||||
descriptor_set = VK_NULL_HANDLE;
|
im_texture = nullptr;
|
||||||
|
|
||||||
v.device.destroyImageView(image_view, v.allocator);
|
v.device.destroyImageView(image_view, v.allocator);
|
||||||
image_view = VK_NULL_HANDLE;
|
image_view = VK_NULL_HANDLE;
|
||||||
|
@ -264,7 +265,7 @@ void UploadTextureData::Destroy() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register a texture
|
// Register a texture
|
||||||
vk::DescriptorSet AddTexture(vk::ImageView image_view, vk::ImageLayout image_layout,
|
ImTextureID AddTexture(vk::ImageView image_view, vk::ImageLayout image_layout,
|
||||||
vk::Sampler sampler) {
|
vk::Sampler sampler) {
|
||||||
VkData* bd = GetBackendData();
|
VkData* bd = GetBackendData();
|
||||||
const InitInfo& v = bd->init_info;
|
const InitInfo& v = bd->init_info;
|
||||||
|
@ -303,7 +304,9 @@ vk::DescriptorSet AddTexture(vk::ImageView image_view, vk::ImageLayout image_lay
|
||||||
};
|
};
|
||||||
v.device.updateDescriptorSets({write_desc}, {});
|
v.device.updateDescriptorSets({write_desc}, {});
|
||||||
}
|
}
|
||||||
return descriptor_set;
|
return new Texture{
|
||||||
|
.descriptor_set = descriptor_set,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
UploadTextureData UploadTexture(const void* data, vk::Format format, u32 width, u32 height,
|
UploadTextureData UploadTexture(const void* data, vk::Format format, u32 width, u32 height,
|
||||||
size_t size) {
|
size_t size) {
|
||||||
|
@ -370,7 +373,7 @@ UploadTextureData UploadTexture(const void* data, vk::Format format, u32 width,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create descriptor set (ImTextureID)
|
// Create descriptor set (ImTextureID)
|
||||||
info.descriptor_set = AddTexture(info.image_view, vk::ImageLayout::eShaderReadOnlyOptimal);
|
info.im_texture = AddTexture(info.image_view, vk::ImageLayout::eShaderReadOnlyOptimal);
|
||||||
|
|
||||||
// Create Upload Buffer
|
// Create Upload Buffer
|
||||||
{
|
{
|
||||||
|
@ -464,10 +467,12 @@ UploadTextureData UploadTexture(const void* data, vk::Format format, u32 width,
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RemoveTexture(vk::DescriptorSet descriptor_set) {
|
void RemoveTexture(ImTextureID texture) {
|
||||||
|
IM_ASSERT(texture != nullptr);
|
||||||
VkData* bd = GetBackendData();
|
VkData* bd = GetBackendData();
|
||||||
const InitInfo& v = bd->init_info;
|
const InitInfo& v = bd->init_info;
|
||||||
v.device.freeDescriptorSets(bd->descriptor_pool, {descriptor_set});
|
v.device.freeDescriptorSets(bd->descriptor_pool, {texture->descriptor_set});
|
||||||
|
delete texture;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void CreateOrResizeBuffer(RenderBuffer& rb, size_t new_size, vk::BufferUsageFlagBits usage) {
|
static void CreateOrResizeBuffer(RenderBuffer& rb, size_t new_size, vk::BufferUsageFlagBits usage) {
|
||||||
|
@ -679,15 +684,11 @@ void RenderDrawData(ImDrawData& draw_data, vk::CommandBuffer command_buffer,
|
||||||
command_buffer.setScissor(0, 1, &scissor);
|
command_buffer.setScissor(0, 1, &scissor);
|
||||||
|
|
||||||
// Bind DescriptorSet with font or user texture
|
// Bind DescriptorSet with font or user texture
|
||||||
vk::DescriptorSet desc_set[1]{(VkDescriptorSet)pcmd->TextureId};
|
vk::DescriptorSet desc_set[1]{pcmd->TextureId->descriptor_set};
|
||||||
if (sizeof(ImTextureID) < sizeof(ImU64)) {
|
|
||||||
// We don't support texture switches if ImTextureID hasn't been redefined to be
|
|
||||||
// 64-bit. Do a flaky check that other textures haven't been used.
|
|
||||||
IM_ASSERT(pcmd->TextureId == (ImTextureID)bd->font_descriptor_set);
|
|
||||||
desc_set[0] = bd->font_descriptor_set;
|
|
||||||
}
|
|
||||||
command_buffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics,
|
command_buffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics,
|
||||||
bd->pipeline_layout, 0, {desc_set}, {});
|
bd->pipeline_layout, 0, {desc_set}, {});
|
||||||
|
command_buffer.setColorBlendEnableEXT(
|
||||||
|
0, {pcmd->TextureId->disable_blend ? vk::False : vk::True});
|
||||||
|
|
||||||
// Draw
|
// Draw
|
||||||
command_buffer.drawIndexed(pcmd->ElemCount, 1, pcmd->IdxOffset + global_idx_offset,
|
command_buffer.drawIndexed(pcmd->ElemCount, 1, pcmd->IdxOffset + global_idx_offset,
|
||||||
|
@ -709,7 +710,7 @@ static bool CreateFontsTexture() {
|
||||||
const InitInfo& v = bd->init_info;
|
const InitInfo& v = bd->init_info;
|
||||||
|
|
||||||
// Destroy existing texture (if any)
|
// Destroy existing texture (if any)
|
||||||
if (bd->font_view || bd->font_image || bd->font_memory || bd->font_descriptor_set) {
|
if (bd->font_view || bd->font_image || bd->font_memory || bd->font_texture) {
|
||||||
CheckVkErr(v.queue.waitIdle());
|
CheckVkErr(v.queue.waitIdle());
|
||||||
DestroyFontsTexture();
|
DestroyFontsTexture();
|
||||||
}
|
}
|
||||||
|
@ -782,7 +783,7 @@ static bool CreateFontsTexture() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the Descriptor Set:
|
// Create the Descriptor Set:
|
||||||
bd->font_descriptor_set = AddTexture(bd->font_view, vk::ImageLayout::eShaderReadOnlyOptimal);
|
bd->font_texture = AddTexture(bd->font_view, vk::ImageLayout::eShaderReadOnlyOptimal);
|
||||||
|
|
||||||
// Create the Upload Buffer:
|
// Create the Upload Buffer:
|
||||||
vk::DeviceMemory upload_buffer_memory{};
|
vk::DeviceMemory upload_buffer_memory{};
|
||||||
|
@ -874,7 +875,7 @@ static bool CreateFontsTexture() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store our identifier
|
// Store our identifier
|
||||||
io.Fonts->SetTexID(bd->font_descriptor_set);
|
io.Fonts->SetTexID(bd->font_texture);
|
||||||
|
|
||||||
// End command buffer
|
// End command buffer
|
||||||
vk::SubmitInfo end_info = {};
|
vk::SubmitInfo end_info = {};
|
||||||
|
@ -898,9 +899,9 @@ static void DestroyFontsTexture() {
|
||||||
VkData* bd = GetBackendData();
|
VkData* bd = GetBackendData();
|
||||||
const InitInfo& v = bd->init_info;
|
const InitInfo& v = bd->init_info;
|
||||||
|
|
||||||
if (bd->font_descriptor_set) {
|
if (bd->font_texture) {
|
||||||
RemoveTexture(bd->font_descriptor_set);
|
RemoveTexture(bd->font_texture);
|
||||||
bd->font_descriptor_set = VK_NULL_HANDLE;
|
bd->font_texture = nullptr;
|
||||||
io.Fonts->SetTexID(nullptr);
|
io.Fonts->SetTexID(nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1057,9 +1058,10 @@ static void CreatePipeline(vk::Device device, const vk::AllocationCallbacks* all
|
||||||
.pAttachments = color_attachment,
|
.pAttachments = color_attachment,
|
||||||
};
|
};
|
||||||
|
|
||||||
vk::DynamicState dynamic_states[2]{
|
vk::DynamicState dynamic_states[3]{
|
||||||
vk::DynamicState::eViewport,
|
vk::DynamicState::eViewport,
|
||||||
vk::DynamicState::eScissor,
|
vk::DynamicState::eScissor,
|
||||||
|
vk::DynamicState::eColorBlendEnableEXT,
|
||||||
};
|
};
|
||||||
vk::PipelineDynamicStateCreateInfo dynamic_state{
|
vk::PipelineDynamicStateCreateInfo dynamic_state{
|
||||||
.dynamicStateCount = (uint32_t)IM_ARRAYSIZE(dynamic_states),
|
.dynamicStateCount = (uint32_t)IM_ARRAYSIZE(dynamic_states),
|
||||||
|
|
|
@ -10,6 +10,13 @@
|
||||||
|
|
||||||
struct ImDrawData;
|
struct ImDrawData;
|
||||||
|
|
||||||
|
namespace ImGui {
|
||||||
|
struct Texture {
|
||||||
|
vk::DescriptorSet descriptor_set{nullptr};
|
||||||
|
bool disable_blend{false};
|
||||||
|
};
|
||||||
|
} // namespace ImGui
|
||||||
|
|
||||||
namespace ImGui::Vulkan {
|
namespace ImGui::Vulkan {
|
||||||
|
|
||||||
struct InitInfo {
|
struct InitInfo {
|
||||||
|
@ -34,29 +41,32 @@ struct InitInfo {
|
||||||
struct UploadTextureData {
|
struct UploadTextureData {
|
||||||
vk::Image image;
|
vk::Image image;
|
||||||
vk::ImageView image_view;
|
vk::ImageView image_view;
|
||||||
vk::DescriptorSet descriptor_set;
|
|
||||||
vk::DeviceMemory image_memory;
|
vk::DeviceMemory image_memory;
|
||||||
|
|
||||||
vk::CommandBuffer command_buffer; // Submit to the queue
|
vk::CommandBuffer command_buffer; // Submit to the queue
|
||||||
vk::Buffer upload_buffer;
|
vk::Buffer upload_buffer;
|
||||||
vk::DeviceMemory upload_buffer_memory;
|
vk::DeviceMemory upload_buffer_memory;
|
||||||
|
|
||||||
|
ImTextureID im_texture;
|
||||||
|
|
||||||
void Upload();
|
void Upload();
|
||||||
|
|
||||||
void Destroy();
|
void Destroy();
|
||||||
};
|
};
|
||||||
|
|
||||||
vk::DescriptorSet AddTexture(vk::ImageView image_view, vk::ImageLayout image_layout,
|
ImTextureID AddTexture(vk::ImageView image_view, vk::ImageLayout image_layout,
|
||||||
vk::Sampler sampler = VK_NULL_HANDLE);
|
vk::Sampler sampler = VK_NULL_HANDLE);
|
||||||
|
|
||||||
UploadTextureData UploadTexture(const void* data, vk::Format format, u32 width, u32 height,
|
UploadTextureData UploadTexture(const void* data, vk::Format format, u32 width, u32 height,
|
||||||
size_t size);
|
size_t size);
|
||||||
|
|
||||||
void RemoveTexture(vk::DescriptorSet descriptor_set);
|
void RemoveTexture(ImTextureID descriptor_set);
|
||||||
|
|
||||||
bool Init(InitInfo info);
|
bool Init(InitInfo info);
|
||||||
void Shutdown();
|
void Shutdown();
|
||||||
void RenderDrawData(ImDrawData& draw_data, vk::CommandBuffer command_buffer,
|
void RenderDrawData(ImDrawData& draw_data, vk::CommandBuffer command_buffer,
|
||||||
vk::Pipeline pipeline = VK_NULL_HANDLE);
|
vk::Pipeline pipeline = VK_NULL_HANDLE);
|
||||||
|
|
||||||
|
void SetBlendEnabled(bool enabled);
|
||||||
|
|
||||||
} // namespace ImGui::Vulkan
|
} // namespace ImGui::Vulkan
|
|
@ -4,6 +4,7 @@
|
||||||
#include <deque>
|
#include <deque>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
|
#include <imgui.h>
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/config.h"
|
#include "common/config.h"
|
||||||
#include "common/io_file.h"
|
#include "common/io_file.h"
|
||||||
|
@ -123,7 +124,7 @@ static std::deque<UploadJob> g_upload_list;
|
||||||
namespace Core::TextureManager {
|
namespace Core::TextureManager {
|
||||||
|
|
||||||
Inner::~Inner() {
|
Inner::~Inner() {
|
||||||
if (upload_data.descriptor_set != nullptr) {
|
if (upload_data.im_texture != nullptr) {
|
||||||
std::unique_lock lk{g_upload_mtx};
|
std::unique_lock lk{g_upload_mtx};
|
||||||
g_upload_list.emplace_back(UploadJob{
|
g_upload_list.emplace_back(UploadJob{
|
||||||
.data = this->upload_data,
|
.data = this->upload_data,
|
||||||
|
@ -239,7 +240,7 @@ void Submit() {
|
||||||
}
|
}
|
||||||
if (upload.core != nullptr) {
|
if (upload.core != nullptr) {
|
||||||
upload.core->upload_data.Upload();
|
upload.core->upload_data.Upload();
|
||||||
upload.core->texture_id = upload.core->upload_data.descriptor_set;
|
upload.core->texture_id = upload.core->upload_data.im_texture;
|
||||||
if (upload.core->count.fetch_sub(1) == 1) {
|
if (upload.core->count.fetch_sub(1) == 1) {
|
||||||
delete upload.core;
|
delete upload.core;
|
||||||
}
|
}
|
||||||
|
|
|
@ -383,6 +383,7 @@ bool Instance::CreateDevice() {
|
||||||
.extendedDynamicState = true,
|
.extendedDynamicState = true,
|
||||||
},
|
},
|
||||||
vk::PhysicalDeviceExtendedDynamicState3FeaturesEXT{
|
vk::PhysicalDeviceExtendedDynamicState3FeaturesEXT{
|
||||||
|
.extendedDynamicState3ColorBlendEnable = true,
|
||||||
.extendedDynamicState3ColorWriteMask = true,
|
.extendedDynamicState3ColorWriteMask = true,
|
||||||
},
|
},
|
||||||
vk::PhysicalDeviceDepthClipControlFeaturesEXT{
|
vk::PhysicalDeviceDepthClipControlFeaturesEXT{
|
||||||
|
|
|
@ -20,6 +20,9 @@
|
||||||
|
|
||||||
#include <vk_mem_alloc.h>
|
#include <vk_mem_alloc.h>
|
||||||
|
|
||||||
|
#include <imgui.h>
|
||||||
|
#include "imgui/renderer/imgui_impl_vulkan.h"
|
||||||
|
|
||||||
namespace Vulkan {
|
namespace Vulkan {
|
||||||
|
|
||||||
bool CanBlitToSwapchain(const vk::PhysicalDevice physical_device, vk::Format format) {
|
bool CanBlitToSwapchain(const vk::PhysicalDevice physical_device, vk::Format format) {
|
||||||
|
@ -103,17 +106,6 @@ static vk::Rect2D FitImage(s32 frame_width, s32 frame_height, s32 swapchain_widt
|
||||||
dst_rect.offset.x, dst_rect.offset.y);
|
dst_rect.offset.x, dst_rect.offset.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
static vk::Format FormatToUnorm(vk::Format fmt) {
|
|
||||||
switch (fmt) {
|
|
||||||
case vk::Format::eR8G8B8A8Srgb:
|
|
||||||
return vk::Format::eR8G8B8A8Unorm;
|
|
||||||
case vk::Format::eB8G8R8A8Srgb:
|
|
||||||
return vk::Format::eB8G8R8A8Unorm;
|
|
||||||
default:
|
|
||||||
UNREACHABLE();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Presenter::CreatePostProcessPipeline() {
|
void Presenter::CreatePostProcessPipeline() {
|
||||||
static const std::array pp_shaders{
|
static const std::array pp_shaders{
|
||||||
HostShaders::FS_TRI_VERT,
|
HostShaders::FS_TRI_VERT,
|
||||||
|
@ -324,9 +316,6 @@ Presenter::Presenter(Frontend::WindowSDL& window_, AmdGpu::Liverpool* liverpool_
|
||||||
|
|
||||||
CreatePostProcessPipeline();
|
CreatePostProcessPipeline();
|
||||||
|
|
||||||
// Setup ImGui
|
|
||||||
ImGui::Core::Initialize(instance, window, num_images,
|
|
||||||
FormatToUnorm(swapchain.GetSurfaceFormat().format));
|
|
||||||
ImGui::Layer::AddLayer(Common::Singleton<Core::Devtools::Layer>::Instance());
|
ImGui::Layer::AddLayer(Common::Singleton<Core::Devtools::Layer>::Instance());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -344,6 +333,9 @@ Presenter::~Presenter() {
|
||||||
|
|
||||||
void Presenter::RecreateFrame(Frame* frame, u32 width, u32 height) {
|
void Presenter::RecreateFrame(Frame* frame, u32 width, u32 height) {
|
||||||
const vk::Device device = instance.GetDevice();
|
const vk::Device device = instance.GetDevice();
|
||||||
|
if (frame->imgui_texture) {
|
||||||
|
ImGui::Vulkan::RemoveTexture(frame->imgui_texture);
|
||||||
|
}
|
||||||
if (frame->image_view) {
|
if (frame->image_view) {
|
||||||
device.destroyImageView(frame->image_view);
|
device.destroyImageView(frame->image_view);
|
||||||
}
|
}
|
||||||
|
@ -361,7 +353,7 @@ void Presenter::RecreateFrame(Frame* frame, u32 width, u32 height) {
|
||||||
.arrayLayers = 1,
|
.arrayLayers = 1,
|
||||||
.samples = vk::SampleCountFlagBits::e1,
|
.samples = vk::SampleCountFlagBits::e1,
|
||||||
.usage = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eTransferDst |
|
.usage = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eTransferDst |
|
||||||
vk::ImageUsageFlagBits::eTransferSrc,
|
vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eSampled,
|
||||||
};
|
};
|
||||||
|
|
||||||
const VmaAllocationCreateInfo alloc_info = {
|
const VmaAllocationCreateInfo alloc_info = {
|
||||||
|
@ -403,6 +395,64 @@ void Presenter::RecreateFrame(Frame* frame, u32 width, u32 height) {
|
||||||
frame->image_view = view;
|
frame->image_view = view;
|
||||||
frame->width = width;
|
frame->width = width;
|
||||||
frame->height = height;
|
frame->height = height;
|
||||||
|
|
||||||
|
frame->imgui_texture = ImGui::Vulkan::AddTexture(view, vk::ImageLayout::eShaderReadOnlyOptimal);
|
||||||
|
frame->imgui_texture->disable_blend = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Frame* Presenter::PrepareLastFrame() {
|
||||||
|
if (last_submit_frame == nullptr) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Frame* frame = last_submit_frame;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
vk::Result result = instance.GetDevice().waitForFences(frame->present_done, false,
|
||||||
|
std::numeric_limits<u64>::max());
|
||||||
|
if (result == vk::Result::eSuccess) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (result == vk::Result::eTimeout) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ASSERT_MSG(result != vk::Result::eErrorDeviceLost,
|
||||||
|
"Device lost during waiting for a frame");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& scheduler = flip_scheduler;
|
||||||
|
scheduler.EndRendering();
|
||||||
|
const auto cmdbuf = scheduler.CommandBuffer();
|
||||||
|
|
||||||
|
const auto frame_subresources = vk::ImageSubresourceRange{
|
||||||
|
.aspectMask = vk::ImageAspectFlagBits::eColor,
|
||||||
|
.baseMipLevel = 0,
|
||||||
|
.levelCount = 1,
|
||||||
|
.baseArrayLayer = 0,
|
||||||
|
.layerCount = VK_REMAINING_ARRAY_LAYERS,
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto pre_barrier =
|
||||||
|
vk::ImageMemoryBarrier2{.srcStageMask = vk::PipelineStageFlagBits2::eColorAttachmentOutput,
|
||||||
|
.srcAccessMask = vk::AccessFlagBits2::eColorAttachmentRead,
|
||||||
|
.dstStageMask = vk::PipelineStageFlagBits2::eColorAttachmentOutput,
|
||||||
|
.dstAccessMask = vk::AccessFlagBits2::eColorAttachmentWrite,
|
||||||
|
.oldLayout = vk::ImageLayout::eShaderReadOnlyOptimal,
|
||||||
|
.newLayout = vk::ImageLayout::eGeneral,
|
||||||
|
.image = frame->image,
|
||||||
|
.subresourceRange{frame_subresources}};
|
||||||
|
|
||||||
|
cmdbuf.pipelineBarrier2(vk::DependencyInfo{
|
||||||
|
.imageMemoryBarrierCount = 1,
|
||||||
|
.pImageMemoryBarriers = &pre_barrier,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Flush frame creation commands.
|
||||||
|
frame->ready_semaphore = scheduler.GetMasterSemaphore()->Handle();
|
||||||
|
frame->ready_tick = scheduler.CurrentTick();
|
||||||
|
SubmitInfo info{};
|
||||||
|
scheduler.Flush(info);
|
||||||
|
return frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Presenter::ShowSplash(Frame* frame /*= nullptr*/) {
|
bool Presenter::ShowSplash(Frame* frame /*= nullptr*/) {
|
||||||
|
@ -499,6 +549,14 @@ Frame* Presenter::PrepareFrameInternal(VideoCore::ImageId image_id, bool is_eop)
|
||||||
// Request a free presentation frame.
|
// Request a free presentation frame.
|
||||||
Frame* frame = GetRenderFrame();
|
Frame* frame = GetRenderFrame();
|
||||||
|
|
||||||
|
if (image_id != VideoCore::NULL_IMAGE_ID) {
|
||||||
|
const auto& image = texture_cache.GetImage(image_id);
|
||||||
|
const auto extent = image.info.size;
|
||||||
|
if (frame->width != extent.width || frame->height != extent.height) {
|
||||||
|
RecreateFrame(frame, extent.width, extent.height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// EOP flips are triggered from GPU thread so use the drawing scheduler to record
|
// EOP flips are triggered from GPU thread so use the drawing scheduler to record
|
||||||
// commands. Otherwise we are dealing with a CPU flip which could have arrived
|
// commands. Otherwise we are dealing with a CPU flip which could have arrived
|
||||||
// from any guest thread. Use a separate scheduler for that.
|
// from any guest thread. Use a separate scheduler for that.
|
||||||
|
@ -515,8 +573,8 @@ Frame* Presenter::PrepareFrameInternal(VideoCore::ImageId image_id, bool is_eop)
|
||||||
};
|
};
|
||||||
|
|
||||||
const auto pre_barrier =
|
const auto pre_barrier =
|
||||||
vk::ImageMemoryBarrier2{.srcStageMask = vk::PipelineStageFlagBits2::eTransfer,
|
vk::ImageMemoryBarrier2{.srcStageMask = vk::PipelineStageFlagBits2::eColorAttachmentOutput,
|
||||||
.srcAccessMask = vk::AccessFlagBits2::eTransferRead,
|
.srcAccessMask = vk::AccessFlagBits2::eColorAttachmentRead,
|
||||||
.dstStageMask = vk::PipelineStageFlagBits2::eColorAttachmentOutput,
|
.dstStageMask = vk::PipelineStageFlagBits2::eColorAttachmentOutput,
|
||||||
.dstAccessMask = vk::AccessFlagBits2::eColorAttachmentWrite,
|
.dstAccessMask = vk::AccessFlagBits2::eColorAttachmentWrite,
|
||||||
.oldLayout = vk::ImageLayout::eUndefined,
|
.oldLayout = vk::ImageLayout::eUndefined,
|
||||||
|
@ -627,17 +685,19 @@ Frame* Presenter::PrepareFrameInternal(VideoCore::ImageId image_id, bool is_eop)
|
||||||
return frame;
|
return frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Presenter::Present(Frame* frame) {
|
void Presenter::Present(Frame* frame, bool is_reusing_frame) {
|
||||||
// Free the frame for reuse
|
// Free the frame for reuse
|
||||||
const auto free_frame = [&] {
|
const auto free_frame = [&] {
|
||||||
|
if (!is_reusing_frame) {
|
||||||
|
last_submit_frame = frame;
|
||||||
std::scoped_lock fl{free_mutex};
|
std::scoped_lock fl{free_mutex};
|
||||||
free_queue.push(frame);
|
free_queue.push(frame);
|
||||||
free_cv.notify_one();
|
free_cv.notify_one();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Recreate the swapchain if the window was resized.
|
// Recreate the swapchain if the window was resized.
|
||||||
if (window.GetWidth() != swapchain.GetExtent().width ||
|
if (window.GetWidth() != swapchain.GetWidth() || window.GetHeight() != swapchain.GetHeight()) {
|
||||||
window.GetHeight() != swapchain.GetExtent().height) {
|
|
||||||
swapchain.Recreate(window.GetWidth(), window.GetHeight());
|
swapchain.Recreate(window.GetWidth(), window.GetHeight());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -656,15 +716,14 @@ void Presenter::Present(Frame* frame) {
|
||||||
// the frame's present fence and future GetRenderFrame() call will hang waiting for this frame.
|
// the frame's present fence and future GetRenderFrame() call will hang waiting for this frame.
|
||||||
instance.GetDevice().resetFences(frame->present_done);
|
instance.GetDevice().resetFences(frame->present_done);
|
||||||
|
|
||||||
ImGui::Core::NewFrame();
|
ImGuiID dockId = ImGui::Core::NewFrame(is_reusing_frame);
|
||||||
|
|
||||||
const vk::Image swapchain_image = swapchain.Image();
|
const vk::Image swapchain_image = swapchain.Image();
|
||||||
|
const vk::ImageView swapchain_image_view = swapchain.ImageView();
|
||||||
|
|
||||||
auto& scheduler = present_scheduler;
|
auto& scheduler = present_scheduler;
|
||||||
const auto cmdbuf = scheduler.CommandBuffer();
|
const auto cmdbuf = scheduler.CommandBuffer();
|
||||||
|
|
||||||
ImGui::Core::Render(cmdbuf, frame);
|
|
||||||
|
|
||||||
{
|
{
|
||||||
auto* profiler_ctx = instance.GetProfilerContext();
|
auto* profiler_ctx = instance.GetProfilerContext();
|
||||||
TracyVkNamedZoneC(profiler_ctx, renderer_gpu_zone, cmdbuf, "Host frame",
|
TracyVkNamedZoneC(profiler_ctx, renderer_gpu_zone, cmdbuf, "Host frame",
|
||||||
|
@ -674,9 +733,9 @@ void Presenter::Present(Frame* frame) {
|
||||||
const std::array pre_barriers{
|
const std::array pre_barriers{
|
||||||
vk::ImageMemoryBarrier{
|
vk::ImageMemoryBarrier{
|
||||||
.srcAccessMask = vk::AccessFlagBits::eNone,
|
.srcAccessMask = vk::AccessFlagBits::eNone,
|
||||||
.dstAccessMask = vk::AccessFlagBits::eTransferWrite,
|
.dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite,
|
||||||
.oldLayout = vk::ImageLayout::eUndefined,
|
.oldLayout = vk::ImageLayout::eUndefined,
|
||||||
.newLayout = vk::ImageLayout::eTransferDstOptimal,
|
.newLayout = vk::ImageLayout::eColorAttachmentOptimal,
|
||||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||||
.image = swapchain_image,
|
.image = swapchain_image,
|
||||||
|
@ -690,9 +749,9 @@ void Presenter::Present(Frame* frame) {
|
||||||
},
|
},
|
||||||
vk::ImageMemoryBarrier{
|
vk::ImageMemoryBarrier{
|
||||||
.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite,
|
.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite,
|
||||||
.dstAccessMask = vk::AccessFlagBits::eTransferRead,
|
.dstAccessMask = vk::AccessFlagBits::eColorAttachmentRead,
|
||||||
.oldLayout = vk::ImageLayout::eGeneral,
|
.oldLayout = vk::ImageLayout::eGeneral,
|
||||||
.newLayout = vk::ImageLayout::eTransferSrcOptimal,
|
.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal,
|
||||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||||
.image = frame->image,
|
.image = frame->image,
|
||||||
|
@ -705,10 +764,11 @@ void Presenter::Present(Frame* frame) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const vk::ImageMemoryBarrier post_barrier{
|
const vk::ImageMemoryBarrier post_barrier{
|
||||||
.srcAccessMask = vk::AccessFlagBits::eTransferWrite,
|
.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite,
|
||||||
.dstAccessMask = vk::AccessFlagBits::eMemoryRead,
|
.dstAccessMask = vk::AccessFlagBits::eMemoryRead,
|
||||||
.oldLayout = vk::ImageLayout::eTransferDstOptimal,
|
.oldLayout = vk::ImageLayout::eColorAttachmentOptimal,
|
||||||
.newLayout = vk::ImageLayout::ePresentSrcKHR,
|
.newLayout = vk::ImageLayout::ePresentSrcKHR,
|
||||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||||
|
@ -723,14 +783,29 @@ void Presenter::Present(Frame* frame) {
|
||||||
};
|
};
|
||||||
|
|
||||||
cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eColorAttachmentOutput,
|
cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eColorAttachmentOutput,
|
||||||
vk::PipelineStageFlagBits::eTransfer,
|
vk::PipelineStageFlagBits::eColorAttachmentOutput,
|
||||||
vk::DependencyFlagBits::eByRegion, {}, {}, pre_barriers);
|
vk::DependencyFlagBits::eByRegion, {}, {}, pre_barriers);
|
||||||
|
|
||||||
cmdbuf.blitImage(
|
{ // Draw the game
|
||||||
frame->image, vk::ImageLayout::eTransferSrcOptimal, swapchain_image,
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2{0.0f});
|
||||||
vk::ImageLayout::eTransferDstOptimal,
|
ImGui::SetNextWindowDockID(dockId, ImGuiCond_Once);
|
||||||
MakeImageBlitStretch(frame->width, frame->height, extent.width, extent.height),
|
ImGui::Begin("Display##game_display", nullptr, ImGuiWindowFlags_NoNav);
|
||||||
vk::Filter::eLinear);
|
|
||||||
|
ImVec2 contentArea = ImGui::GetContentRegionAvail();
|
||||||
|
const vk::Rect2D imgRect =
|
||||||
|
FitImage(frame->width, frame->height, (s32)contentArea.x, (s32)contentArea.y);
|
||||||
|
ImGui::SetCursorPos(ImGui::GetCursorStartPos() + ImVec2{
|
||||||
|
(float)imgRect.offset.x,
|
||||||
|
(float)imgRect.offset.y,
|
||||||
|
});
|
||||||
|
ImGui::Image(frame->imgui_texture, {
|
||||||
|
static_cast<float>(imgRect.extent.width),
|
||||||
|
static_cast<float>(imgRect.extent.height),
|
||||||
|
});
|
||||||
|
ImGui::End();
|
||||||
|
ImGui::PopStyleVar();
|
||||||
|
}
|
||||||
|
ImGui::Core::Render(cmdbuf, swapchain_image_view, swapchain.GetExtent());
|
||||||
|
|
||||||
cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eAllCommands,
|
cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eAllCommands,
|
||||||
vk::PipelineStageFlagBits::eAllCommands,
|
vk::PipelineStageFlagBits::eAllCommands,
|
||||||
|
@ -756,7 +831,9 @@ void Presenter::Present(Frame* frame) {
|
||||||
}
|
}
|
||||||
|
|
||||||
free_frame();
|
free_frame();
|
||||||
|
if (!is_reusing_frame) {
|
||||||
DebugState.IncFlipFrameNum();
|
DebugState.IncFlipFrameNum();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Frame* Presenter::GetRenderFrame() {
|
Frame* Presenter::GetRenderFrame() {
|
||||||
|
@ -790,9 +867,9 @@ Frame* Presenter::GetRenderFrame() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the window dimensions changed, recreate this frame
|
// Initialize default frame image
|
||||||
if (frame->width != window.GetWidth() || frame->height != window.GetHeight()) {
|
if (frame->width == 0 || frame->height == 0) {
|
||||||
RecreateFrame(frame, window.GetWidth(), window.GetHeight());
|
RecreateFrame(frame, 1920, 1080);
|
||||||
}
|
}
|
||||||
|
|
||||||
return frame;
|
return frame;
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
|
|
||||||
|
#include "imgui/imgui_config.h"
|
||||||
#include "video_core/amdgpu/liverpool.h"
|
#include "video_core/amdgpu/liverpool.h"
|
||||||
#include "video_core/renderer_vulkan/vk_instance.h"
|
#include "video_core/renderer_vulkan/vk_instance.h"
|
||||||
#include "video_core/renderer_vulkan/vk_scheduler.h"
|
#include "video_core/renderer_vulkan/vk_scheduler.h"
|
||||||
|
@ -30,6 +31,8 @@ struct Frame {
|
||||||
vk::Fence present_done;
|
vk::Fence present_done;
|
||||||
vk::Semaphore ready_semaphore;
|
vk::Semaphore ready_semaphore;
|
||||||
u64 ready_tick;
|
u64 ready_tick;
|
||||||
|
|
||||||
|
ImTextureID imgui_texture;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum SchedulerType {
|
enum SchedulerType {
|
||||||
|
@ -86,8 +89,9 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ShowSplash(Frame* frame = nullptr);
|
bool ShowSplash(Frame* frame = nullptr);
|
||||||
void Present(Frame* frame);
|
void Present(Frame* frame, bool is_reusing_frame = false);
|
||||||
void RecreateFrame(Frame* frame, u32 width, u32 height);
|
void RecreateFrame(Frame* frame, u32 width, u32 height);
|
||||||
|
Frame* PrepareLastFrame();
|
||||||
|
|
||||||
void FlushDraw() {
|
void FlushDraw() {
|
||||||
SubmitInfo info{};
|
SubmitInfo info{};
|
||||||
|
@ -121,6 +125,7 @@ private:
|
||||||
vk::UniqueCommandPool command_pool;
|
vk::UniqueCommandPool command_pool;
|
||||||
std::vector<Frame> present_frames;
|
std::vector<Frame> present_frames;
|
||||||
std::queue<Frame*> free_queue;
|
std::queue<Frame*> free_queue;
|
||||||
|
Frame* last_submit_frame;
|
||||||
std::mutex free_mutex;
|
std::mutex free_mutex;
|
||||||
std::condition_variable free_cv;
|
std::condition_variable free_cv;
|
||||||
std::condition_variable_any frame_cv;
|
std::condition_variable_any frame_cv;
|
||||||
|
|
|
@ -4,8 +4,8 @@
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/config.h"
|
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
|
#include "imgui/renderer/imgui_core.h"
|
||||||
#include "sdl_window.h"
|
#include "sdl_window.h"
|
||||||
#include "video_core/renderer_vulkan/vk_instance.h"
|
#include "video_core/renderer_vulkan/vk_instance.h"
|
||||||
#include "video_core/renderer_vulkan/vk_swapchain.h"
|
#include "video_core/renderer_vulkan/vk_swapchain.h"
|
||||||
|
@ -15,7 +15,9 @@ namespace Vulkan {
|
||||||
Swapchain::Swapchain(const Instance& instance_, const Frontend::WindowSDL& window)
|
Swapchain::Swapchain(const Instance& instance_, const Frontend::WindowSDL& window)
|
||||||
: instance{instance_}, surface{CreateSurface(instance.GetInstance(), window)} {
|
: instance{instance_}, surface{CreateSurface(instance.GetInstance(), window)} {
|
||||||
FindPresentFormat();
|
FindPresentFormat();
|
||||||
Create(window.GetWidth(), window.GetHeight(), surface);
|
|
||||||
|
Create(window.GetWidth(), window.GetHeight());
|
||||||
|
ImGui::Core::Initialize(instance, window, image_count, surface_format.format);
|
||||||
}
|
}
|
||||||
|
|
||||||
Swapchain::~Swapchain() {
|
Swapchain::~Swapchain() {
|
||||||
|
@ -23,10 +25,9 @@ Swapchain::~Swapchain() {
|
||||||
instance.GetInstance().destroySurfaceKHR(surface);
|
instance.GetInstance().destroySurfaceKHR(surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Swapchain::Create(u32 width_, u32 height_, vk::SurfaceKHR surface_) {
|
void Swapchain::Create(u32 width_, u32 height_) {
|
||||||
width = width_;
|
width = width_;
|
||||||
height = height_;
|
height = height_;
|
||||||
surface = surface_;
|
|
||||||
needs_recreation = false;
|
needs_recreation = false;
|
||||||
|
|
||||||
Destroy();
|
Destroy();
|
||||||
|
@ -86,7 +87,7 @@ void Swapchain::Create(u32 width_, u32 height_, vk::SurfaceKHR surface_) {
|
||||||
|
|
||||||
void Swapchain::Recreate(u32 width_, u32 height_) {
|
void Swapchain::Recreate(u32 width_, u32 height_) {
|
||||||
LOG_DEBUG(Render_Vulkan, "Recreate the swapchain: width={} height={}", width_, height_);
|
LOG_DEBUG(Render_Vulkan, "Recreate the swapchain: width={} height={}", width_, height_);
|
||||||
Create(width_, height_, surface);
|
Create(width_, height_);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Swapchain::AcquireNextImage() {
|
bool Swapchain::AcquireNextImage() {
|
||||||
|
@ -209,10 +210,14 @@ void Swapchain::Destroy() {
|
||||||
if (swapchain) {
|
if (swapchain) {
|
||||||
device.destroySwapchainKHR(swapchain);
|
device.destroySwapchainKHR(swapchain);
|
||||||
}
|
}
|
||||||
for (u32 i = 0; i < image_count; i++) {
|
|
||||||
device.destroySemaphore(image_acquired[i]);
|
for (const auto& sem : image_acquired) {
|
||||||
device.destroySemaphore(present_ready[i]);
|
device.destroySemaphore(sem);
|
||||||
}
|
}
|
||||||
|
for (const auto& sem : present_ready) {
|
||||||
|
device.destroySemaphore(sem);
|
||||||
|
}
|
||||||
|
|
||||||
image_acquired.clear();
|
image_acquired.clear();
|
||||||
present_ready.clear();
|
present_ready.clear();
|
||||||
}
|
}
|
||||||
|
@ -249,9 +254,30 @@ void Swapchain::SetupImages() {
|
||||||
vk::to_string(images_result));
|
vk::to_string(images_result));
|
||||||
images = std::move(imgs);
|
images = std::move(imgs);
|
||||||
image_count = static_cast<u32>(images.size());
|
image_count = static_cast<u32>(images.size());
|
||||||
|
images_view.resize(image_count);
|
||||||
|
for (u32 i = 0; i < image_count; ++i) {
|
||||||
|
if (images_view[i]) {
|
||||||
|
device.destroyImageView(images_view[i]);
|
||||||
|
}
|
||||||
|
auto [im_view_result, im_view] = device.createImageView(vk::ImageViewCreateInfo{
|
||||||
|
.image = images[i],
|
||||||
|
.viewType = vk::ImageViewType::e2D,
|
||||||
|
.format = FormatToUnorm(surface_format.format),
|
||||||
|
.subresourceRange =
|
||||||
|
{
|
||||||
|
.aspectMask = vk::ImageAspectFlagBits::eColor,
|
||||||
|
.levelCount = 1,
|
||||||
|
.layerCount = 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
ASSERT_MSG(im_view_result == vk::Result::eSuccess, "Failed to create image view: {}",
|
||||||
|
vk::to_string(im_view_result));
|
||||||
|
images_view[i] = im_view;
|
||||||
|
}
|
||||||
|
|
||||||
for (u32 i = 0; i < image_count; ++i) {
|
for (u32 i = 0; i < image_count; ++i) {
|
||||||
SetObjectName(device, images[i], "Swapchain Image {}", i);
|
SetObjectName(device, images[i], "Swapchain Image {}", i);
|
||||||
|
SetObjectName(device, images_view[i], "Swapchain ImageView {}", i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,13 +17,24 @@ namespace Vulkan {
|
||||||
class Instance;
|
class Instance;
|
||||||
class Scheduler;
|
class Scheduler;
|
||||||
|
|
||||||
|
inline vk::Format FormatToUnorm(vk::Format fmt) {
|
||||||
|
switch (fmt) {
|
||||||
|
case vk::Format::eR8G8B8A8Srgb:
|
||||||
|
return vk::Format::eR8G8B8A8Unorm;
|
||||||
|
case vk::Format::eB8G8R8A8Srgb:
|
||||||
|
return vk::Format::eB8G8R8A8Unorm;
|
||||||
|
default:
|
||||||
|
UNREACHABLE();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class Swapchain {
|
class Swapchain {
|
||||||
public:
|
public:
|
||||||
explicit Swapchain(const Instance& instance, const Frontend::WindowSDL& window);
|
explicit Swapchain(const Instance& instance, const Frontend::WindowSDL& window);
|
||||||
~Swapchain();
|
~Swapchain();
|
||||||
|
|
||||||
/// Creates (or recreates) the swapchain with a given size.
|
/// Creates (or recreates) the swapchain with a given size.
|
||||||
void Create(u32 width, u32 height, vk::SurfaceKHR surface);
|
void Create(u32 width, u32 height);
|
||||||
|
|
||||||
/// Recreates the swapchain with a given size and current surface.
|
/// Recreates the swapchain with a given size and current surface.
|
||||||
void Recreate(u32 width, u32 height);
|
void Recreate(u32 width, u32 height);
|
||||||
|
@ -42,6 +53,10 @@ public:
|
||||||
return images[image_index];
|
return images[image_index];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vk::ImageView ImageView() const {
|
||||||
|
return images_view[image_index];
|
||||||
|
}
|
||||||
|
|
||||||
vk::SurfaceFormatKHR GetSurfaceFormat() const {
|
vk::SurfaceFormatKHR GetSurfaceFormat() const {
|
||||||
return surface_format;
|
return surface_format;
|
||||||
}
|
}
|
||||||
|
@ -103,6 +118,7 @@ private:
|
||||||
vk::SurfaceTransformFlagBitsKHR transform;
|
vk::SurfaceTransformFlagBitsKHR transform;
|
||||||
vk::CompositeAlphaFlagBitsKHR composite_alpha;
|
vk::CompositeAlphaFlagBitsKHR composite_alpha;
|
||||||
std::vector<vk::Image> images;
|
std::vector<vk::Image> images;
|
||||||
|
std::vector<vk::ImageView> images_view;
|
||||||
std::vector<vk::Semaphore> image_acquired;
|
std::vector<vk::Semaphore> image_acquired;
|
||||||
std::vector<vk::Semaphore> present_ready;
|
std::vector<vk::Semaphore> present_ready;
|
||||||
u32 width = 0;
|
u32 width = 0;
|
||||||
|
|
Loading…
Reference in a new issue