mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2025-06-12 05:33:15 +00:00
Render without rendering (#2152)
* 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
19 changed files with 306 additions and 110 deletions
|
@ -383,6 +383,7 @@ bool Instance::CreateDevice() {
|
|||
.extendedDynamicState = true,
|
||||
},
|
||||
vk::PhysicalDeviceExtendedDynamicState3FeaturesEXT{
|
||||
.extendedDynamicState3ColorBlendEnable = true,
|
||||
.extendedDynamicState3ColorWriteMask = true,
|
||||
},
|
||||
vk::PhysicalDeviceDepthClipControlFeaturesEXT{
|
||||
|
|
|
@ -20,6 +20,9 @@
|
|||
|
||||
#include <vk_mem_alloc.h>
|
||||
|
||||
#include <imgui.h>
|
||||
#include "imgui/renderer/imgui_impl_vulkan.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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() {
|
||||
static const std::array pp_shaders{
|
||||
HostShaders::FS_TRI_VERT,
|
||||
|
@ -324,9 +316,6 @@ Presenter::Presenter(Frontend::WindowSDL& window_, AmdGpu::Liverpool* liverpool_
|
|||
|
||||
CreatePostProcessPipeline();
|
||||
|
||||
// Setup ImGui
|
||||
ImGui::Core::Initialize(instance, window, num_images,
|
||||
FormatToUnorm(swapchain.GetSurfaceFormat().format));
|
||||
ImGui::Layer::AddLayer(Common::Singleton<Core::Devtools::Layer>::Instance());
|
||||
}
|
||||
|
||||
|
@ -344,6 +333,9 @@ Presenter::~Presenter() {
|
|||
|
||||
void Presenter::RecreateFrame(Frame* frame, u32 width, u32 height) {
|
||||
const vk::Device device = instance.GetDevice();
|
||||
if (frame->imgui_texture) {
|
||||
ImGui::Vulkan::RemoveTexture(frame->imgui_texture);
|
||||
}
|
||||
if (frame->image_view) {
|
||||
device.destroyImageView(frame->image_view);
|
||||
}
|
||||
|
@ -361,7 +353,7 @@ void Presenter::RecreateFrame(Frame* frame, u32 width, u32 height) {
|
|||
.arrayLayers = 1,
|
||||
.samples = vk::SampleCountFlagBits::e1,
|
||||
.usage = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eTransferDst |
|
||||
vk::ImageUsageFlagBits::eTransferSrc,
|
||||
vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eSampled,
|
||||
};
|
||||
|
||||
const VmaAllocationCreateInfo alloc_info = {
|
||||
|
@ -403,6 +395,64 @@ void Presenter::RecreateFrame(Frame* frame, u32 width, u32 height) {
|
|||
frame->image_view = view;
|
||||
frame->width = width;
|
||||
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*/) {
|
||||
|
@ -499,6 +549,14 @@ Frame* Presenter::PrepareFrameInternal(VideoCore::ImageId image_id, bool is_eop)
|
|||
// Request a free presentation frame.
|
||||
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
|
||||
// commands. Otherwise we are dealing with a CPU flip which could have arrived
|
||||
// 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 =
|
||||
vk::ImageMemoryBarrier2{.srcStageMask = vk::PipelineStageFlagBits2::eTransfer,
|
||||
.srcAccessMask = vk::AccessFlagBits2::eTransferRead,
|
||||
vk::ImageMemoryBarrier2{.srcStageMask = vk::PipelineStageFlagBits2::eColorAttachmentOutput,
|
||||
.srcAccessMask = vk::AccessFlagBits2::eColorAttachmentRead,
|
||||
.dstStageMask = vk::PipelineStageFlagBits2::eColorAttachmentOutput,
|
||||
.dstAccessMask = vk::AccessFlagBits2::eColorAttachmentWrite,
|
||||
.oldLayout = vk::ImageLayout::eUndefined,
|
||||
|
@ -627,17 +685,19 @@ Frame* Presenter::PrepareFrameInternal(VideoCore::ImageId image_id, bool is_eop)
|
|||
return frame;
|
||||
}
|
||||
|
||||
void Presenter::Present(Frame* frame) {
|
||||
void Presenter::Present(Frame* frame, bool is_reusing_frame) {
|
||||
// Free the frame for reuse
|
||||
const auto free_frame = [&] {
|
||||
std::scoped_lock fl{free_mutex};
|
||||
free_queue.push(frame);
|
||||
free_cv.notify_one();
|
||||
if (!is_reusing_frame) {
|
||||
last_submit_frame = frame;
|
||||
std::scoped_lock fl{free_mutex};
|
||||
free_queue.push(frame);
|
||||
free_cv.notify_one();
|
||||
}
|
||||
};
|
||||
|
||||
// Recreate the swapchain if the window was resized.
|
||||
if (window.GetWidth() != swapchain.GetExtent().width ||
|
||||
window.GetHeight() != swapchain.GetExtent().height) {
|
||||
if (window.GetWidth() != swapchain.GetWidth() || window.GetHeight() != swapchain.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.
|
||||
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::ImageView swapchain_image_view = swapchain.ImageView();
|
||||
|
||||
auto& scheduler = present_scheduler;
|
||||
const auto cmdbuf = scheduler.CommandBuffer();
|
||||
|
||||
ImGui::Core::Render(cmdbuf, frame);
|
||||
|
||||
{
|
||||
auto* profiler_ctx = instance.GetProfilerContext();
|
||||
TracyVkNamedZoneC(profiler_ctx, renderer_gpu_zone, cmdbuf, "Host frame",
|
||||
|
@ -674,9 +733,9 @@ void Presenter::Present(Frame* frame) {
|
|||
const std::array pre_barriers{
|
||||
vk::ImageMemoryBarrier{
|
||||
.srcAccessMask = vk::AccessFlagBits::eNone,
|
||||
.dstAccessMask = vk::AccessFlagBits::eTransferWrite,
|
||||
.dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite,
|
||||
.oldLayout = vk::ImageLayout::eUndefined,
|
||||
.newLayout = vk::ImageLayout::eTransferDstOptimal,
|
||||
.newLayout = vk::ImageLayout::eColorAttachmentOptimal,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = swapchain_image,
|
||||
|
@ -690,9 +749,9 @@ void Presenter::Present(Frame* frame) {
|
|||
},
|
||||
vk::ImageMemoryBarrier{
|
||||
.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite,
|
||||
.dstAccessMask = vk::AccessFlagBits::eTransferRead,
|
||||
.dstAccessMask = vk::AccessFlagBits::eColorAttachmentRead,
|
||||
.oldLayout = vk::ImageLayout::eGeneral,
|
||||
.newLayout = vk::ImageLayout::eTransferSrcOptimal,
|
||||
.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = frame->image,
|
||||
|
@ -705,10 +764,11 @@ void Presenter::Present(Frame* frame) {
|
|||
},
|
||||
},
|
||||
};
|
||||
|
||||
const vk::ImageMemoryBarrier post_barrier{
|
||||
.srcAccessMask = vk::AccessFlagBits::eTransferWrite,
|
||||
.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite,
|
||||
.dstAccessMask = vk::AccessFlagBits::eMemoryRead,
|
||||
.oldLayout = vk::ImageLayout::eTransferDstOptimal,
|
||||
.oldLayout = vk::ImageLayout::eColorAttachmentOptimal,
|
||||
.newLayout = vk::ImageLayout::ePresentSrcKHR,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
|
@ -723,14 +783,29 @@ void Presenter::Present(Frame* frame) {
|
|||
};
|
||||
|
||||
cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eColorAttachmentOutput,
|
||||
vk::PipelineStageFlagBits::eTransfer,
|
||||
vk::PipelineStageFlagBits::eColorAttachmentOutput,
|
||||
vk::DependencyFlagBits::eByRegion, {}, {}, pre_barriers);
|
||||
|
||||
cmdbuf.blitImage(
|
||||
frame->image, vk::ImageLayout::eTransferSrcOptimal, swapchain_image,
|
||||
vk::ImageLayout::eTransferDstOptimal,
|
||||
MakeImageBlitStretch(frame->width, frame->height, extent.width, extent.height),
|
||||
vk::Filter::eLinear);
|
||||
{ // Draw the game
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2{0.0f});
|
||||
ImGui::SetNextWindowDockID(dockId, ImGuiCond_Once);
|
||||
ImGui::Begin("Display##game_display", nullptr, ImGuiWindowFlags_NoNav);
|
||||
|
||||
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,
|
||||
vk::PipelineStageFlagBits::eAllCommands,
|
||||
|
@ -756,7 +831,9 @@ void Presenter::Present(Frame* frame) {
|
|||
}
|
||||
|
||||
free_frame();
|
||||
DebugState.IncFlipFrameNum();
|
||||
if (!is_reusing_frame) {
|
||||
DebugState.IncFlipFrameNum();
|
||||
}
|
||||
}
|
||||
|
||||
Frame* Presenter::GetRenderFrame() {
|
||||
|
@ -790,9 +867,9 @@ Frame* Presenter::GetRenderFrame() {
|
|||
}
|
||||
}
|
||||
|
||||
// If the window dimensions changed, recreate this frame
|
||||
if (frame->width != window.GetWidth() || frame->height != window.GetHeight()) {
|
||||
RecreateFrame(frame, window.GetWidth(), window.GetHeight());
|
||||
// Initialize default frame image
|
||||
if (frame->width == 0 || frame->height == 0) {
|
||||
RecreateFrame(frame, 1920, 1080);
|
||||
}
|
||||
|
||||
return frame;
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#include <condition_variable>
|
||||
|
||||
#include "imgui/imgui_config.h"
|
||||
#include "video_core/amdgpu/liverpool.h"
|
||||
#include "video_core/renderer_vulkan/vk_instance.h"
|
||||
#include "video_core/renderer_vulkan/vk_scheduler.h"
|
||||
|
@ -30,6 +31,8 @@ struct Frame {
|
|||
vk::Fence present_done;
|
||||
vk::Semaphore ready_semaphore;
|
||||
u64 ready_tick;
|
||||
|
||||
ImTextureID imgui_texture;
|
||||
};
|
||||
|
||||
enum SchedulerType {
|
||||
|
@ -86,8 +89,9 @@ public:
|
|||
}
|
||||
|
||||
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);
|
||||
Frame* PrepareLastFrame();
|
||||
|
||||
void FlushDraw() {
|
||||
SubmitInfo info{};
|
||||
|
@ -121,6 +125,7 @@ private:
|
|||
vk::UniqueCommandPool command_pool;
|
||||
std::vector<Frame> present_frames;
|
||||
std::queue<Frame*> free_queue;
|
||||
Frame* last_submit_frame;
|
||||
std::mutex free_mutex;
|
||||
std::condition_variable free_cv;
|
||||
std::condition_variable_any frame_cv;
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include "common/assert.h"
|
||||
#include "common/config.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "imgui/renderer/imgui_core.h"
|
||||
#include "sdl_window.h"
|
||||
#include "video_core/renderer_vulkan/vk_instance.h"
|
||||
#include "video_core/renderer_vulkan/vk_swapchain.h"
|
||||
|
@ -15,7 +15,9 @@ namespace Vulkan {
|
|||
Swapchain::Swapchain(const Instance& instance_, const Frontend::WindowSDL& window)
|
||||
: instance{instance_}, surface{CreateSurface(instance.GetInstance(), window)} {
|
||||
FindPresentFormat();
|
||||
Create(window.GetWidth(), window.GetHeight(), surface);
|
||||
|
||||
Create(window.GetWidth(), window.GetHeight());
|
||||
ImGui::Core::Initialize(instance, window, image_count, surface_format.format);
|
||||
}
|
||||
|
||||
Swapchain::~Swapchain() {
|
||||
|
@ -23,10 +25,9 @@ Swapchain::~Swapchain() {
|
|||
instance.GetInstance().destroySurfaceKHR(surface);
|
||||
}
|
||||
|
||||
void Swapchain::Create(u32 width_, u32 height_, vk::SurfaceKHR surface_) {
|
||||
void Swapchain::Create(u32 width_, u32 height_) {
|
||||
width = width_;
|
||||
height = height_;
|
||||
surface = surface_;
|
||||
needs_recreation = false;
|
||||
|
||||
Destroy();
|
||||
|
@ -86,7 +87,7 @@ void Swapchain::Create(u32 width_, u32 height_, vk::SurfaceKHR surface_) {
|
|||
|
||||
void Swapchain::Recreate(u32 width_, u32 height_) {
|
||||
LOG_DEBUG(Render_Vulkan, "Recreate the swapchain: width={} height={}", width_, height_);
|
||||
Create(width_, height_, surface);
|
||||
Create(width_, height_);
|
||||
}
|
||||
|
||||
bool Swapchain::AcquireNextImage() {
|
||||
|
@ -209,10 +210,14 @@ void Swapchain::Destroy() {
|
|||
if (swapchain) {
|
||||
device.destroySwapchainKHR(swapchain);
|
||||
}
|
||||
for (u32 i = 0; i < image_count; i++) {
|
||||
device.destroySemaphore(image_acquired[i]);
|
||||
device.destroySemaphore(present_ready[i]);
|
||||
|
||||
for (const auto& sem : image_acquired) {
|
||||
device.destroySemaphore(sem);
|
||||
}
|
||||
for (const auto& sem : present_ready) {
|
||||
device.destroySemaphore(sem);
|
||||
}
|
||||
|
||||
image_acquired.clear();
|
||||
present_ready.clear();
|
||||
}
|
||||
|
@ -249,9 +254,30 @@ void Swapchain::SetupImages() {
|
|||
vk::to_string(images_result));
|
||||
images = std::move(imgs);
|
||||
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) {
|
||||
SetObjectName(device, images[i], "Swapchain Image {}", i);
|
||||
SetObjectName(device, images_view[i], "Swapchain ImageView {}", i);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,13 +17,24 @@ namespace Vulkan {
|
|||
class Instance;
|
||||
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 {
|
||||
public:
|
||||
explicit Swapchain(const Instance& instance, const Frontend::WindowSDL& window);
|
||||
~Swapchain();
|
||||
|
||||
/// 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.
|
||||
void Recreate(u32 width, u32 height);
|
||||
|
@ -42,6 +53,10 @@ public:
|
|||
return images[image_index];
|
||||
}
|
||||
|
||||
vk::ImageView ImageView() const {
|
||||
return images_view[image_index];
|
||||
}
|
||||
|
||||
vk::SurfaceFormatKHR GetSurfaceFormat() const {
|
||||
return surface_format;
|
||||
}
|
||||
|
@ -103,6 +118,7 @@ private:
|
|||
vk::SurfaceTransformFlagBitsKHR transform;
|
||||
vk::CompositeAlphaFlagBitsKHR composite_alpha;
|
||||
std::vector<vk::Image> images;
|
||||
std::vector<vk::ImageView> images_view;
|
||||
std::vector<vk::Semaphore> image_acquired;
|
||||
std::vector<vk::Semaphore> present_ready;
|
||||
u32 width = 0;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue