Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/Common/GPU/Vulkan/VulkanRenderManager.cpp
5669 views
1
#include <cstdint>
2
3
#include <map>
4
#include <sstream>
5
6
#include "Common/Log.h"
7
#include "Common/StringUtils.h"
8
#include "Common/TimeUtil.h"
9
10
#include "Common/GPU/Vulkan/VulkanAlloc.h"
11
#include "Common/GPU/Vulkan/VulkanContext.h"
12
#include "Common/GPU/Vulkan/VulkanRenderManager.h"
13
14
#include "Common/LogReporting.h"
15
#include "Common/Thread/ThreadUtil.h"
16
17
#if 0 // def _DEBUG
18
#define VLOG(...) NOTICE_LOG(Log::G3D, __VA_ARGS__)
19
#else
20
#define VLOG(...)
21
#endif
22
23
#ifndef UINT64_MAX
24
#define UINT64_MAX 0xFFFFFFFFFFFFFFFFULL
25
#endif
26
27
using namespace PPSSPP_VK;
28
29
// renderPass is an example of the "compatibility class" or RenderPassType type.
30
bool VKRGraphicsPipeline::Create(VulkanContext *vulkan, VkRenderPass compatibleRenderPass, RenderPassType rpType, VkSampleCountFlagBits sampleCount, double scheduleTime, int countToCompile) {
31
// Good torture test to test the shutdown-while-precompiling-shaders issue on PC where it's normally
32
// hard to catch because shaders compile so fast.
33
// sleep_ms(200);
34
35
bool multisample = RenderPassTypeHasMultisample(rpType);
36
if (multisample) {
37
if (sampleCount_ != VK_SAMPLE_COUNT_FLAG_BITS_MAX_ENUM) {
38
_assert_(sampleCount == sampleCount_);
39
} else {
40
sampleCount_ = sampleCount;
41
}
42
}
43
44
// Sanity check.
45
// Seen in crash reports from PowerVR GE8320, presumably we failed creating some shader modules.
46
if (!desc->vertexShader || !desc->fragmentShader) {
47
ERROR_LOG(Log::G3D, "Failed creating graphics pipeline - missing vs/fs shader module pointers!");
48
pipeline[(size_t)rpType]->Post(VK_NULL_HANDLE);
49
return false;
50
}
51
52
// Fill in the last part of the desc since now it's time to block.
53
VkShaderModule vs = desc->vertexShader->BlockUntilReady();
54
VkShaderModule fs = desc->fragmentShader->BlockUntilReady();
55
VkShaderModule gs = desc->geometryShader ? desc->geometryShader->BlockUntilReady() : VK_NULL_HANDLE;
56
57
if (!vs || !fs || (!gs && desc->geometryShader)) {
58
ERROR_LOG(Log::G3D, "Failed creating graphics pipeline - missing shader modules");
59
pipeline[(size_t)rpType]->Post(VK_NULL_HANDLE);
60
return false;
61
}
62
63
if (!compatibleRenderPass) {
64
ERROR_LOG(Log::G3D, "Failed creating graphics pipeline - compatible render pass was nullptr");
65
pipeline[(size_t)rpType]->Post(VK_NULL_HANDLE);
66
return false;
67
}
68
69
uint32_t stageCount = 2;
70
VkPipelineShaderStageCreateInfo ss[3]{};
71
ss[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
72
ss[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
73
ss[0].pSpecializationInfo = nullptr;
74
ss[0].module = vs;
75
ss[0].pName = "main";
76
ss[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
77
ss[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
78
ss[1].pSpecializationInfo = nullptr;
79
ss[1].module = fs;
80
ss[1].pName = "main";
81
if (gs) {
82
stageCount++;
83
ss[2].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
84
ss[2].stage = VK_SHADER_STAGE_GEOMETRY_BIT;
85
ss[2].pSpecializationInfo = nullptr;
86
ss[2].module = gs;
87
ss[2].pName = "main";
88
}
89
90
VkGraphicsPipelineCreateInfo pipe{ VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO };
91
pipe.pStages = ss;
92
pipe.stageCount = stageCount;
93
pipe.renderPass = compatibleRenderPass;
94
pipe.basePipelineIndex = 0;
95
pipe.pColorBlendState = &desc->cbs;
96
pipe.pDepthStencilState = &desc->dss;
97
pipe.pRasterizationState = &desc->rs;
98
99
VkPipelineMultisampleStateCreateInfo ms{ VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO };
100
ms.rasterizationSamples = multisample ? sampleCount : VK_SAMPLE_COUNT_1_BIT;
101
if (multisample && (flags_ & PipelineFlags::USES_DISCARD)) {
102
// Extreme quality
103
ms.sampleShadingEnable = true;
104
ms.minSampleShading = 1.0f;
105
}
106
107
VkPipelineInputAssemblyStateCreateInfo inputAssembly{ VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO };
108
inputAssembly.topology = desc->topology;
109
110
// We will use dynamic viewport state.
111
pipe.pVertexInputState = &desc->vis;
112
pipe.pViewportState = &desc->views;
113
pipe.pTessellationState = nullptr;
114
pipe.pDynamicState = &desc->ds;
115
pipe.pInputAssemblyState = &inputAssembly;
116
pipe.pMultisampleState = &ms;
117
pipe.layout = desc->pipelineLayout->pipelineLayout;
118
pipe.basePipelineHandle = VK_NULL_HANDLE;
119
pipe.basePipelineIndex = 0;
120
pipe.subpass = 0;
121
122
double start = time_now_d();
123
VkPipeline vkpipeline;
124
VkResult result = vkCreateGraphicsPipelines(vulkan->GetDevice(), desc->pipelineCache, 1, &pipe, nullptr, &vkpipeline);
125
126
double now = time_now_d();
127
double taken_ms_since_scheduling = (now - scheduleTime) * 1000.0;
128
double taken_ms = (now - start) * 1000.0;
129
130
#ifndef _DEBUG
131
if (taken_ms < 0.1) {
132
DEBUG_LOG(Log::G3D, "Pipeline (x/%d) time on %s: %0.2f ms, %0.2f ms since scheduling (fast) rpType: %04x sampleBits: %d (%s)",
133
countToCompile, GetCurrentThreadName(), taken_ms, taken_ms_since_scheduling, (u32)rpType, (u32)sampleCount, tag_.c_str());
134
} else {
135
INFO_LOG(Log::G3D, "Pipeline (x/%d) time on %s: %0.2f ms, %0.2f ms since scheduling rpType: %04x sampleBits: %d (%s)",
136
countToCompile, GetCurrentThreadName(), taken_ms, taken_ms_since_scheduling, (u32)rpType, (u32)sampleCount, tag_.c_str());
137
}
138
#endif
139
140
bool success = true;
141
if (result == VK_INCOMPLETE) {
142
// Bad (disallowed by spec) return value seen on Adreno in Burnout :( Try to ignore?
143
// Would really like to log more here, we could probably attach more info to desc.
144
//
145
// At least create a null placeholder to avoid creating over and over if something is broken.
146
pipeline[(size_t)rpType]->Post(VK_NULL_HANDLE);
147
ERROR_LOG(Log::G3D, "Failed creating graphics pipeline! VK_INCOMPLETE");
148
LogCreationFailure();
149
success = false;
150
} else if (result != VK_SUCCESS) {
151
pipeline[(size_t)rpType]->Post(VK_NULL_HANDLE);
152
ERROR_LOG(Log::G3D, "Failed creating graphics pipeline! result='%s'", VulkanResultToString(result));
153
LogCreationFailure();
154
success = false;
155
} else {
156
// Success!
157
if (!tag_.empty()) {
158
vulkan->SetDebugName(vkpipeline, VK_OBJECT_TYPE_PIPELINE, tag_.c_str());
159
}
160
pipeline[(size_t)rpType]->Post(vkpipeline);
161
}
162
163
return success;
164
}
165
166
void VKRGraphicsPipeline::DestroyVariants(VulkanContext *vulkan, bool msaaOnly) {
167
for (size_t i = 0; i < (size_t)RenderPassType::TYPE_COUNT; i++) {
168
if (!this->pipeline[i])
169
continue;
170
if (msaaOnly && (i & (int)RenderPassType::MULTISAMPLE) == 0)
171
continue;
172
173
VkPipeline pipeline = this->pipeline[i]->BlockUntilReady();
174
// pipeline can be nullptr here, if it failed to compile before.
175
if (pipeline) {
176
vulkan->Delete().QueueDeletePipeline(pipeline);
177
}
178
this->pipeline[i] = nullptr;
179
}
180
sampleCount_ = VK_SAMPLE_COUNT_FLAG_BITS_MAX_ENUM;
181
}
182
183
void VKRGraphicsPipeline::DestroyVariantsInstant(VkDevice device) {
184
for (size_t i = 0; i < (size_t)RenderPassType::TYPE_COUNT; i++) {
185
if (pipeline[i]) {
186
vkDestroyPipeline(device, pipeline[i]->BlockUntilReady(), nullptr);
187
delete pipeline[i];
188
pipeline[i] = nullptr;
189
}
190
}
191
}
192
193
VKRGraphicsPipeline::~VKRGraphicsPipeline() {
194
// This is called from the callbacked queued in QueueForDeletion.
195
// When we reach here, we should already be empty, so let's assert on that.
196
for (size_t i = 0; i < (size_t)RenderPassType::TYPE_COUNT; i++) {
197
_assert_(!pipeline[i]);
198
}
199
if (desc)
200
desc->Release();
201
}
202
203
void VKRGraphicsPipeline::BlockUntilCompiled() {
204
for (size_t i = 0; i < (size_t)RenderPassType::TYPE_COUNT; i++) {
205
if (pipeline[i]) {
206
pipeline[i]->BlockUntilReady();
207
}
208
}
209
}
210
211
void VKRGraphicsPipeline::QueueForDeletion(VulkanContext *vulkan) {
212
// Can't destroy variants here, the pipeline still lives for a while.
213
vulkan->Delete().QueueCallback([](VulkanContext *vulkan, void *p) {
214
VKRGraphicsPipeline *pipeline = (VKRGraphicsPipeline *)p;
215
pipeline->DestroyVariantsInstant(vulkan->GetDevice());
216
delete pipeline;
217
}, this);
218
}
219
220
u32 VKRGraphicsPipeline::GetVariantsBitmask() const {
221
u32 bitmask = 0;
222
for (size_t i = 0; i < (size_t)RenderPassType::TYPE_COUNT; i++) {
223
if (pipeline[i]) {
224
bitmask |= 1 << i;
225
}
226
}
227
return bitmask;
228
}
229
230
void VKRGraphicsPipeline::LogCreationFailure() const {
231
ERROR_LOG(Log::G3D, "vs: %s\n[END VS]", desc->vertexShaderSource.c_str());
232
ERROR_LOG(Log::G3D, "fs: %s\n[END FS]", desc->fragmentShaderSource.c_str());
233
if (desc->geometryShader) {
234
ERROR_LOG(Log::G3D, "gs: %s\n[END GS]", desc->geometryShaderSource.c_str());
235
}
236
// TODO: Maybe log various other state?
237
ERROR_LOG(Log::G3D, "======== END OF PIPELINE ==========");
238
}
239
240
struct SinglePipelineTask {
241
VKRGraphicsPipeline *pipeline;
242
VkRenderPass compatibleRenderPass;
243
RenderPassType rpType;
244
VkSampleCountFlagBits sampleCount;
245
double scheduleTime;
246
int countToCompile;
247
};
248
249
class CreateMultiPipelinesTask : public Task {
250
public:
251
CreateMultiPipelinesTask(VulkanContext *vulkan, std::vector<SinglePipelineTask> tasks) : vulkan_(vulkan), tasks_(std::move(tasks)) {
252
tasksInFlight_.fetch_add(1);
253
}
254
~CreateMultiPipelinesTask() = default;
255
256
TaskType Type() const override {
257
return TaskType::CPU_COMPUTE;
258
}
259
260
TaskPriority Priority() const override {
261
return TaskPriority::HIGH;
262
}
263
264
void Run() override {
265
for (auto &task : tasks_) {
266
task.pipeline->Create(vulkan_, task.compatibleRenderPass, task.rpType, task.sampleCount, task.scheduleTime, task.countToCompile);
267
}
268
tasksInFlight_.fetch_sub(1);
269
}
270
271
VulkanContext *vulkan_;
272
std::vector<SinglePipelineTask> tasks_;
273
274
// Use during shutdown to make sure there aren't any leftover tasks sitting queued.
275
// Could probably be done more elegantly. Like waiting for all tasks of a type, or saving pointers to them, or something...
276
// Returns the maximum value of tasks in flight seen during the wait.
277
static int WaitForAll();
278
static std::atomic<int> tasksInFlight_;
279
};
280
281
int CreateMultiPipelinesTask::WaitForAll() {
282
int inFlight = 0;
283
int maxInFlight = 0;
284
while ((inFlight = tasksInFlight_.load()) > 0) {
285
if (inFlight > maxInFlight) {
286
maxInFlight = inFlight;
287
}
288
sleep_ms(2, "create-multi-pipelines-wait");
289
}
290
return maxInFlight;
291
}
292
293
std::atomic<int> CreateMultiPipelinesTask::tasksInFlight_;
294
295
VulkanRenderManager::VulkanRenderManager(VulkanContext *vulkan, bool useThread, HistoryBuffer<FrameTimeData, FRAME_TIME_HISTORY_LENGTH> &frameTimeHistory)
296
: vulkan_(vulkan), queueRunner_(vulkan),
297
initTimeMs_("initTimeMs"),
298
totalGPUTimeMs_("totalGPUTimeMs"),
299
renderCPUTimeMs_("renderCPUTimeMs"),
300
descUpdateTimeMs_("descUpdateCPUTimeMs"),
301
useRenderThread_(useThread),
302
frameTimeHistory_(frameTimeHistory)
303
{
304
inflightFramesAtStart_ = vulkan_->GetInflightFrames();
305
306
// For present timing experiments. Disabled for now.
307
measurePresentTime_ = false;
308
309
frameDataShared_.Init(vulkan, useThread, measurePresentTime_);
310
311
for (int i = 0; i < inflightFramesAtStart_; i++) {
312
frameData_[i].Init(vulkan, i);
313
}
314
315
queueRunner_.CreateDeviceObjects();
316
}
317
318
bool VulkanRenderManager::CreateBackbuffers() {
319
if (!vulkan_->IsSwapchainInited()) {
320
ERROR_LOG(Log::G3D, "No swapchain - can't create backbuffers");
321
return false;
322
}
323
324
VkCommandBuffer cmdInit = GetInitCmd();
325
326
if (vulkan_->HasRealSwapchain()) {
327
if (!CreateSwapchainViewsAndDepth(cmdInit, &postInitBarrier_, frameDataShared_)) {
328
return false;
329
}
330
}
331
332
curWidthRaw_ = -1;
333
curHeightRaw_ = -1;
334
335
if (newInflightFrames_ != -1) {
336
INFO_LOG(Log::G3D, "Updating inflight frames to %d", newInflightFrames_);
337
vulkan_->UpdateInflightFrames(newInflightFrames_);
338
newInflightFrames_ = -1;
339
}
340
341
outOfDateFrames_ = 0;
342
343
for (int i = 0; i < vulkan_->GetInflightFrames(); i++) {
344
auto &frameData = frameData_[i];
345
frameData.readyForFence = true; // Just in case.
346
}
347
348
// Start the thread(s).
349
StartThreads();
350
return true;
351
}
352
353
bool VulkanRenderManager::CreateSwapchainViewsAndDepth(VkCommandBuffer cmdInit, VulkanBarrierBatch *barriers, FrameDataShared &frameDataShared) {
354
VkResult res = vkGetSwapchainImagesKHR(vulkan_->GetDevice(), vulkan_->GetSwapchain(), &frameDataShared.swapchainImageCount_, nullptr);
355
_dbg_assert_(res == VK_SUCCESS);
356
357
VkImage *swapchainImages = new VkImage[frameDataShared.swapchainImageCount_];
358
res = vkGetSwapchainImagesKHR(vulkan_->GetDevice(), vulkan_->GetSwapchain(), &frameDataShared.swapchainImageCount_, swapchainImages);
359
if (res != VK_SUCCESS) {
360
ERROR_LOG(Log::G3D, "vkGetSwapchainImagesKHR failed");
361
delete[] swapchainImages;
362
return false;
363
}
364
365
static const VkSemaphoreCreateInfo semaphoreCreateInfo = { VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO };
366
for (uint32_t i = 0; i < frameDataShared.swapchainImageCount_; i++) {
367
SwapchainImageData sc_buffer{};
368
sc_buffer.image = swapchainImages[i];
369
res = vkCreateSemaphore(vulkan_->GetDevice(), &semaphoreCreateInfo, nullptr, &sc_buffer.renderingCompleteSemaphore);
370
_dbg_assert_(res == VK_SUCCESS);
371
372
VkImageViewCreateInfo color_image_view = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO };
373
color_image_view.format = vulkan_->GetSwapchainFormat();
374
color_image_view.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
375
color_image_view.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
376
color_image_view.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
377
color_image_view.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
378
color_image_view.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
379
color_image_view.subresourceRange.baseMipLevel = 0;
380
color_image_view.subresourceRange.levelCount = 1;
381
color_image_view.subresourceRange.baseArrayLayer = 0;
382
color_image_view.subresourceRange.layerCount = 1; // TODO: Investigate hw-assisted stereo.
383
color_image_view.viewType = VK_IMAGE_VIEW_TYPE_2D;
384
color_image_view.flags = 0;
385
color_image_view.image = sc_buffer.image;
386
387
// We leave the images as UNDEFINED, there's no need to pre-transition them as
388
// the backbuffer renderpass starts out with them being auto-transitioned from UNDEFINED anyway.
389
// Also, turns out it's illegal to transition un-acquired images, thanks Hans-Kristian. See #11417.
390
391
res = vkCreateImageView(vulkan_->GetDevice(), &color_image_view, nullptr, &sc_buffer.view);
392
vulkan_->SetDebugName(sc_buffer.view, VK_OBJECT_TYPE_IMAGE_VIEW, "swapchain_view");
393
frameDataShared.swapchainImages_.push_back(sc_buffer);
394
_dbg_assert_(res == VK_SUCCESS);
395
}
396
delete[] swapchainImages;
397
398
// Must be before InitBackbufferRenderPass.
399
if (queueRunner_.InitDepthStencilBuffer(cmdInit, barriers)) {
400
queueRunner_.InitBackbufferFramebuffers(vulkan_->GetBackbufferWidth(), vulkan_->GetBackbufferHeight(), frameDataShared);
401
}
402
return true;
403
}
404
405
void VulkanRenderManager::StartThreads() {
406
{
407
std::unique_lock<std::mutex> lock(compileQueueMutex_);
408
_assert_(compileQueue_.empty());
409
}
410
411
runCompileThread_ = true; // For controlling the compiler thread's exit
412
413
if (useRenderThread_) {
414
INFO_LOG(Log::G3D, "Starting Vulkan submission thread");
415
renderThread_ = std::thread(&VulkanRenderManager::RenderThreadFunc, this);
416
}
417
INFO_LOG(Log::G3D, "Starting Vulkan compiler thread");
418
compileThread_ = std::thread(&VulkanRenderManager::CompileThreadFunc, this);
419
420
if (measurePresentTime_ && vulkan_->Extensions().KHR_present_wait && vulkan_->GetPresentMode() == VK_PRESENT_MODE_FIFO_KHR) {
421
INFO_LOG(Log::G3D, "Starting Vulkan present wait thread");
422
presentWaitThread_ = std::thread(&VulkanRenderManager::PresentWaitThreadFunc, this);
423
}
424
}
425
426
// MUST be called from emuthread!
427
void VulkanRenderManager::StopThreads() {
428
INFO_LOG(Log::G3D, "VulkanRenderManager::StopThreads");
429
// Make sure we don't have an open non-backbuffer render pass
430
if (curRenderStep_ && curRenderStep_->render.framebuffer != nullptr) {
431
EndCurRenderStep();
432
}
433
// Not sure this is a sensible check - should be ok even if not.
434
// _dbg_assert_(steps_.empty());
435
436
if (useRenderThread_) {
437
_dbg_assert_(renderThread_.joinable());
438
// Tell the render thread to quit when it's done.
439
VKRRenderThreadTask *task = new VKRRenderThreadTask(VKRRunType::EXIT);
440
task->frame = vulkan_->GetCurFrame();
441
{
442
std::unique_lock<std::mutex> lock(pushMutex_);
443
renderThreadQueue_.push(task);
444
}
445
pushCondVar_.notify_one();
446
// Once the render thread encounters the above exit task, it'll exit.
447
renderThread_.join();
448
INFO_LOG(Log::G3D, "Vulkan submission thread joined. Frame=%d", vulkan_->GetCurFrame());
449
}
450
451
for (int i = 0; i < vulkan_->GetInflightFrames(); i++) {
452
auto &frameData = frameData_[i];
453
// Zero the queries so we don't try to pull them later.
454
frameData.profile.timestampDescriptions.clear();
455
}
456
457
{
458
std::unique_lock<std::mutex> lock(compileQueueMutex_);
459
runCompileThread_ = false; // Compiler and present thread both look at this bool.
460
_assert_(compileThread_.joinable());
461
compileCond_.notify_one();
462
}
463
compileThread_.join();
464
465
if (presentWaitThread_.joinable()) {
466
presentWaitThread_.join();
467
}
468
469
INFO_LOG(Log::G3D, "Vulkan compiler thread joined. Now wait for any straggling compile tasks. runCompileThread_ = %d", (int)runCompileThread_);
470
CreateMultiPipelinesTask::WaitForAll();
471
472
{
473
std::unique_lock<std::mutex> lock(compileQueueMutex_);
474
_assert_(compileQueue_.empty());
475
}
476
}
477
478
void VulkanRenderManager::DestroyBackbuffers() {
479
StopThreads();
480
vulkan_->WaitUntilQueueIdle();
481
482
for (auto &image : frameDataShared_.swapchainImages_) {
483
vulkan_->Delete().QueueDeleteImageView(image.view);
484
vkDestroySemaphore(vulkan_->GetDevice(), image.renderingCompleteSemaphore, nullptr);
485
}
486
frameDataShared_.swapchainImages_.clear();
487
frameDataShared_.swapchainImageCount_ = 0;
488
489
queueRunner_.DestroyBackBuffers();
490
}
491
492
// Hm, I'm finding the occasional report of these asserts.
493
void VulkanRenderManager::CheckNothingPending() {
494
_assert_(pipelinesToCheck_.empty());
495
{
496
std::unique_lock<std::mutex> lock(compileQueueMutex_);
497
_assert_(compileQueue_.empty());
498
}
499
}
500
501
VulkanRenderManager::~VulkanRenderManager() {
502
INFO_LOG(Log::G3D, "VulkanRenderManager destructor");
503
504
{
505
std::unique_lock<std::mutex> lock(compileQueueMutex_);
506
_assert_(compileQueue_.empty());
507
}
508
509
if (useRenderThread_) {
510
_dbg_assert_(!renderThread_.joinable());
511
}
512
513
_dbg_assert_(!runCompileThread_); // StopThread should already have been called from DestroyBackbuffers.
514
515
vulkan_->WaitUntilQueueIdle();
516
517
_dbg_assert_(pipelineLayouts_.empty());
518
519
VkDevice device = vulkan_->GetDevice();
520
frameDataShared_.Destroy(vulkan_);
521
for (int i = 0; i < inflightFramesAtStart_; i++) {
522
frameData_[i].Destroy(vulkan_);
523
}
524
queueRunner_.DestroyDeviceObjects();
525
}
526
527
void VulkanRenderManager::CompileThreadFunc() {
528
SetCurrentThreadName("ShaderCompile");
529
while (true) {
530
bool exitAfterCompile = false;
531
std::vector<CompileQueueEntry> toCompile;
532
{
533
std::unique_lock<std::mutex> lock(compileQueueMutex_);
534
while (compileQueue_.empty() && runCompileThread_) {
535
compileCond_.wait(lock);
536
}
537
toCompile = std::move(compileQueue_);
538
compileQueue_.clear();
539
if (!runCompileThread_) {
540
exitAfterCompile = true;
541
}
542
}
543
544
int countToCompile = (int)toCompile.size();
545
546
// Here we sort the pending pipelines by vertex and fragment shaders,
547
std::map<std::pair<Promise<VkShaderModule> *, Promise<VkShaderModule> *>, std::vector<SinglePipelineTask>> map;
548
549
double scheduleTime = time_now_d();
550
551
// Here we sort pending graphics pipelines by vertex and fragment shaders, and split up further.
552
// Those with the same pairs of shaders should be on the same thread, at least on NVIDIA.
553
// I don't think PowerVR cares though, it doesn't seem to reuse information between the compiles,
554
// so we might want a different splitting algorithm there.
555
for (auto &entry : toCompile) {
556
switch (entry.type) {
557
case CompileQueueEntry::Type::GRAPHICS:
558
{
559
map[std::make_pair(entry.graphics->desc->vertexShader, entry.graphics->desc->fragmentShader)].push_back(
560
SinglePipelineTask{
561
entry.graphics,
562
entry.compatibleRenderPass,
563
entry.renderPassType,
564
entry.sampleCount,
565
scheduleTime, // these two are for logging purposes.
566
countToCompile,
567
}
568
);
569
break;
570
}
571
}
572
}
573
574
for (const auto &iter : map) {
575
auto &shaders = iter.first;
576
auto &entries = iter.second;
577
578
// NOTICE_LOG(Log::G3D, "For this shader pair, we have %d pipelines to create", (int)entries.size());
579
580
Task *task = new CreateMultiPipelinesTask(vulkan_, entries);
581
g_threadManager.EnqueueTask(task);
582
}
583
584
if (exitAfterCompile) {
585
break;
586
}
587
588
// Hold off just a bit before we check again, to allow bunches of pipelines to collect.
589
sleep_ms(1, "pipeline-collect");
590
}
591
592
std::unique_lock<std::mutex> lock(compileQueueMutex_);
593
_assert_(compileQueue_.empty());
594
}
595
596
void VulkanRenderManager::RenderThreadFunc() {
597
SetCurrentThreadName("VulkanRenderMan");
598
while (true) {
599
_dbg_assert_(useRenderThread_);
600
601
// Pop a task of the queue and execute it.
602
VKRRenderThreadTask *task = nullptr;
603
{
604
std::unique_lock<std::mutex> lock(pushMutex_);
605
while (renderThreadQueue_.empty()) {
606
pushCondVar_.wait(lock);
607
}
608
task = renderThreadQueue_.front();
609
renderThreadQueue_.pop();
610
}
611
612
// Oh, we got a task! We can now have pushMutex_ unlocked, allowing the host to
613
// push more work when it feels like it, and just start working.
614
if (task->runType == VKRRunType::EXIT) {
615
// Oh, host wanted out. Let's leave.
616
delete task;
617
// In this case, there should be no more tasks.
618
break;
619
}
620
621
Run(*task);
622
delete task;
623
}
624
625
// Wait for the device to be done with everything, before tearing stuff down.
626
// TODO: Do we really need this? It's probably a good idea, though.
627
vkDeviceWaitIdle(vulkan_->GetDevice());
628
VLOG("PULL: Quitting");
629
}
630
631
void VulkanRenderManager::PresentWaitThreadFunc() {
632
SetCurrentThreadName("PresentWait");
633
634
#if !PPSSPP_PLATFORM(IOS_APP_STORE)
635
_dbg_assert_(vkWaitForPresentKHR != nullptr);
636
637
uint64_t waitedId = frameIdGen_;
638
while (runCompileThread_) {
639
const uint64_t timeout = 1000000000ULL; // 1 sec
640
if (VK_SUCCESS == vkWaitForPresentKHR(vulkan_->GetDevice(), vulkan_->GetSwapchain(), waitedId, timeout)) {
641
frameTimeHistory_[waitedId].actualPresent = time_now_d();
642
frameTimeHistory_[waitedId].waitCount++;
643
waitedId++;
644
} else {
645
// We caught up somehow, which is a bad sign (we should have blocked, right?). Maybe we should break out of the loop?
646
sleep_ms(1, "present-wait-problem");
647
frameTimeHistory_[waitedId].waitCount++;
648
}
649
_dbg_assert_(waitedId <= frameIdGen_);
650
}
651
#endif
652
653
INFO_LOG(Log::G3D, "Leaving PresentWaitThreadFunc()");
654
}
655
656
void VulkanRenderManager::PollPresentTiming() {
657
// For VK_GOOGLE_display_timing, we need to poll.
658
659
// Poll for information about completed frames.
660
// NOTE: We seem to get the information pretty late! Like after 6 frames, which is quite weird.
661
// Tested on POCO F4.
662
// TODO: Getting validation errors that this should be called from the thread doing the presenting.
663
// Probably a fair point. For now, we turn it off.
664
if (measurePresentTime_ && vulkan_->Extensions().GOOGLE_display_timing) {
665
uint32_t count = 0;
666
vkGetPastPresentationTimingGOOGLE(vulkan_->GetDevice(), vulkan_->GetSwapchain(), &count, nullptr);
667
if (count > 0) {
668
VkPastPresentationTimingGOOGLE *timings = new VkPastPresentationTimingGOOGLE[count];
669
vkGetPastPresentationTimingGOOGLE(vulkan_->GetDevice(), vulkan_->GetSwapchain(), &count, timings);
670
for (uint32_t i = 0; i < count; i++) {
671
uint64_t presentId = timings[i].presentID;
672
frameTimeHistory_[presentId].actualPresent = from_time_raw(timings[i].actualPresentTime);
673
frameTimeHistory_[presentId].desiredPresentTime = from_time_raw(timings[i].desiredPresentTime);
674
frameTimeHistory_[presentId].earliestPresentTime = from_time_raw(timings[i].earliestPresentTime);
675
double presentMargin = from_time_raw_relative(timings[i].presentMargin);
676
frameTimeHistory_[presentId].presentMargin = presentMargin;
677
}
678
delete[] timings;
679
}
680
}
681
}
682
683
void VulkanRenderManager::BeginFrame(bool enableProfiling, bool enableLogProfiler) {
684
double frameBeginTime = time_now_d()
685
VLOG("BeginFrame");
686
VkDevice device = vulkan_->GetDevice();
687
688
int curFrame = vulkan_->GetCurFrame();
689
FrameData &frameData = frameData_[curFrame];
690
VLOG("PUSH: Fencing %d", curFrame);
691
692
// Makes sure the submission from the previous time around has happened. Otherwise
693
// we are not allowed to wait from another thread here..
694
if (useRenderThread_) {
695
std::unique_lock<std::mutex> lock(frameData.fenceMutex);
696
while (!frameData.readyForFence) {
697
frameData.fenceCondVar.wait(lock);
698
}
699
frameData.readyForFence = false;
700
}
701
702
// This must be the very first Vulkan call we do in a new frame.
703
// Makes sure the very last command buffer from the frame before the previous has been fully executed.
704
if (vkWaitForFences(device, 1, &frameData.fence, true, UINT64_MAX) == VK_ERROR_DEVICE_LOST) {
705
_assert_msg_(false, "Device lost in vkWaitForFences");
706
}
707
vkResetFences(device, 1, &frameData.fence);
708
709
uint64_t frameId = frameIdGen_++;
710
711
PollPresentTiming();
712
713
ResetDescriptorLists(curFrame);
714
715
int validBits = vulkan_->GetQueueFamilyProperties(vulkan_->GetGraphicsQueueFamilyIndex()).timestampValidBits;
716
717
FrameTimeData &frameTimeData = frameTimeHistory_.Add(frameId);
718
frameTimeData.frameId = frameId;
719
frameTimeData.frameBegin = frameBeginTime;
720
frameTimeData.afterFenceWait = time_now_d();
721
722
// Can't set this until after the fence.
723
frameData.profile.enabled = enableProfiling;
724
frameData.profile.timestampsEnabled = enableProfiling && validBits > 0;
725
frameData.frameId = frameId;
726
727
uint64_t queryResults[MAX_TIMESTAMP_QUERIES];
728
729
if (enableProfiling) {
730
// Pull the profiling results from last time and produce a summary!
731
if (!frameData.profile.timestampDescriptions.empty() && frameData.profile.timestampsEnabled) {
732
int numQueries = (int)frameData.profile.timestampDescriptions.size();
733
VkResult res = vkGetQueryPoolResults(
734
vulkan_->GetDevice(),
735
frameData.profile.queryPool, 0, numQueries, sizeof(uint64_t) * numQueries, &queryResults[0], sizeof(uint64_t),
736
VK_QUERY_RESULT_64_BIT);
737
if (res == VK_SUCCESS) {
738
double timestampConversionFactor = (double)vulkan_->GetPhysicalDeviceProperties().properties.limits.timestampPeriod * (1.0 / 1000000.0);
739
uint64_t timestampDiffMask = validBits == 64 ? 0xFFFFFFFFFFFFFFFFULL : ((1ULL << validBits) - 1);
740
std::stringstream str;
741
742
char line[256];
743
totalGPUTimeMs_.Update(((double)((queryResults[numQueries - 1] - queryResults[0]) & timestampDiffMask) * timestampConversionFactor));
744
totalGPUTimeMs_.Format(line, sizeof(line));
745
str << line;
746
renderCPUTimeMs_.Update((frameData.profile.cpuEndTime - frameData.profile.cpuStartTime) * 1000.0);
747
renderCPUTimeMs_.Format(line, sizeof(line));
748
str << line;
749
descUpdateTimeMs_.Update(frameData.profile.descWriteTime * 1000.0);
750
descUpdateTimeMs_.Format(line, sizeof(line));
751
str << line;
752
snprintf(line, sizeof(line), "Descriptors written: %d (dedup: %d)\n", frameData.profile.descriptorsWritten, frameData.profile.descriptorsDeduped);
753
str << line;
754
snprintf(line, sizeof(line), "Resource deletions: %d\n", vulkan_->GetLastDeleteCount());
755
str << line;
756
for (int i = 0; i < numQueries - 1; i++) {
757
uint64_t diff = (queryResults[i + 1] - queryResults[i]) & timestampDiffMask;
758
double milliseconds = (double)diff * timestampConversionFactor;
759
760
// Can't use SimpleStat for these very easily since these are dynamic per frame.
761
// Only the first one is static, the initCmd.
762
// Could try some hashtable tracking for the rest, later.
763
if (i == 0) {
764
initTimeMs_.Update(milliseconds);
765
initTimeMs_.Format(line, sizeof(line));
766
} else {
767
snprintf(line, sizeof(line), "%s: %0.3f ms\n", frameData.profile.timestampDescriptions[i + 1].c_str(), milliseconds);
768
}
769
str << line;
770
}
771
frameData.profile.profileSummary = str.str();
772
} else {
773
frameData.profile.profileSummary = "(error getting GPU profile - not ready?)";
774
}
775
} else {
776
std::stringstream str;
777
char line[256];
778
renderCPUTimeMs_.Update((frameData.profile.cpuEndTime - frameData.profile.cpuStartTime) * 1000.0);
779
renderCPUTimeMs_.Format(line, sizeof(line));
780
str << line;
781
descUpdateTimeMs_.Update(frameData.profile.descWriteTime * 1000.0);
782
descUpdateTimeMs_.Format(line, sizeof(line));
783
str << line;
784
snprintf(line, sizeof(line), "Descriptors written: %d\n", frameData.profile.descriptorsWritten);
785
str << line;
786
frameData.profile.profileSummary = str.str();
787
}
788
789
#ifdef _DEBUG
790
std::string cmdString;
791
for (int i = 0; i < ARRAY_SIZE(frameData.profile.commandCounts); i++) {
792
if (frameData.profile.commandCounts[i] > 0) {
793
cmdString += StringFromFormat("%s: %d\n", VKRRenderCommandToString((VKRRenderCommand)i), frameData.profile.commandCounts[i]);
794
}
795
}
796
memset(frameData.profile.commandCounts, 0, sizeof(frameData.profile.commandCounts));
797
frameData.profile.profileSummary += cmdString;
798
#endif
799
}
800
801
frameData.profile.descriptorsWritten = 0;
802
frameData.profile.descriptorsDeduped = 0;
803
804
// Must be after the fence - this performs deletes.
805
VLOG("PUSH: BeginFrame %d", curFrame);
806
807
insideFrame_ = true;
808
vulkan_->BeginFrame(enableLogProfiler ? GetInitCmd() : VK_NULL_HANDLE);
809
810
frameData.profile.timestampDescriptions.clear();
811
if (frameData.profile.timestampsEnabled) {
812
// For various reasons, we need to always use an init cmd buffer in this case to perform the vkCmdResetQueryPool,
813
// unless we want to limit ourselves to only measure the main cmd buffer.
814
// Later versions of Vulkan have support for clearing queries on the CPU timeline, but we don't want to rely on that.
815
// Reserve the first two queries for initCmd.
816
frameData.profile.timestampDescriptions.emplace_back("initCmd Begin");
817
frameData.profile.timestampDescriptions.emplace_back("initCmd");
818
VkCommandBuffer initCmd = GetInitCmd();
819
}
820
}
821
822
VkCommandBuffer VulkanRenderManager::GetInitCmd() {
823
int curFrame = vulkan_->GetCurFrame();
824
return frameData_[curFrame].GetInitCmd(vulkan_);
825
}
826
827
void VulkanRenderManager::ReportBadStateForDraw() {
828
const char *cause1 = "";
829
char cause2[256];
830
cause2[0] = '\0';
831
if (!curRenderStep_) {
832
cause1 = "No current render step";
833
}
834
if (curRenderStep_ && curRenderStep_->stepType != VKRStepType::RENDER) {
835
cause1 = "Not a render step: ";
836
std::string str = VulkanQueueRunner::StepToString(vulkan_, *curRenderStep_);
837
truncate_cpy(cause2, str);
838
}
839
ERROR_LOG_REPORT_ONCE(baddraw, Log::G3D, "Can't draw: %s%s. Step count: %d", cause1, cause2, (int)steps_.size());
840
}
841
842
int VulkanRenderManager::WaitForPipelines() {
843
return CreateMultiPipelinesTask::WaitForAll();
844
}
845
846
VKRGraphicsPipeline *VulkanRenderManager::CreateGraphicsPipeline(VKRGraphicsPipelineDesc *desc, PipelineFlags pipelineFlags, uint32_t variantBitmask, VkSampleCountFlagBits sampleCount, bool cacheLoad, const char *tag) {
847
if (!desc->vertexShader || !desc->fragmentShader) {
848
ERROR_LOG(Log::G3D, "Can't create graphics pipeline with missing vs/ps: %p %p", desc->vertexShader, desc->fragmentShader);
849
return nullptr;
850
}
851
852
VKRGraphicsPipeline *pipeline = new VKRGraphicsPipeline(pipelineFlags, tag);
853
pipeline->desc = desc;
854
pipeline->desc->AddRef();
855
if (curRenderStep_ && !cacheLoad) {
856
// The common case during gameplay.
857
pipelinesToCheck_.push_back(pipeline);
858
} else {
859
if (!variantBitmask) {
860
WARN_LOG(Log::G3D, "WARNING: Will not compile any variants of pipeline, not in renderpass and empty variantBitmask");
861
}
862
// Presumably we're in initialization, loading the shader cache.
863
// Look at variantBitmask to see what variants we should queue up.
864
RPKey key{
865
VKRRenderPassLoadAction::CLEAR, VKRRenderPassLoadAction::CLEAR, VKRRenderPassLoadAction::CLEAR,
866
VKRRenderPassStoreAction::STORE, VKRRenderPassStoreAction::DONT_CARE, VKRRenderPassStoreAction::DONT_CARE,
867
};
868
VKRRenderPass *compatibleRenderPass = queueRunner_.GetRenderPass(key);
869
std::unique_lock<std::mutex> lock(compileQueueMutex_);
870
_dbg_assert_(runCompileThread_);
871
bool needsCompile = false;
872
for (size_t i = 0; i < (size_t)RenderPassType::TYPE_COUNT; i++) {
873
if (!(variantBitmask & (1 << i)))
874
continue;
875
RenderPassType rpType = (RenderPassType)i;
876
877
// Sanity check - don't compile incompatible types (could be caused by corrupt caches, changes in data structures, etc).
878
if ((pipelineFlags & PipelineFlags::USES_DEPTH_STENCIL) && !RenderPassTypeHasDepth(rpType)) {
879
WARN_LOG(Log::G3D, "Not compiling pipeline that requires depth, for non depth renderpass type");
880
continue;
881
}
882
// Shouldn't hit this, these should have been filtered elsewhere. However, still a good check to do.
883
if (sampleCount == VK_SAMPLE_COUNT_1_BIT && RenderPassTypeHasMultisample(rpType)) {
884
WARN_LOG(Log::G3D, "Not compiling single sample pipeline for a multisampled render pass type");
885
continue;
886
}
887
888
if (rpType == RenderPassType::BACKBUFFER) {
889
sampleCount = VK_SAMPLE_COUNT_1_BIT;
890
}
891
892
// Sanity check
893
if (runCompileThread_) {
894
pipeline->pipeline[i] = Promise<VkPipeline>::CreateEmpty();
895
compileQueue_.emplace_back(pipeline, compatibleRenderPass->Get(vulkan_, rpType, sampleCount), rpType, sampleCount);
896
}
897
needsCompile = true;
898
}
899
if (needsCompile)
900
compileCond_.notify_one();
901
}
902
return pipeline;
903
}
904
905
void VulkanRenderManager::EndCurRenderStep() {
906
if (!curRenderStep_)
907
return;
908
909
_dbg_assert_(runCompileThread_);
910
911
RPKey key{
912
curRenderStep_->render.colorLoad, curRenderStep_->render.depthLoad, curRenderStep_->render.stencilLoad,
913
curRenderStep_->render.colorStore, curRenderStep_->render.depthStore, curRenderStep_->render.stencilStore,
914
};
915
// Save the accumulated pipeline flags so we can use that to configure the render pass.
916
// We'll often be able to avoid loading/saving the depth/stencil buffer.
917
curRenderStep_->render.pipelineFlags = curPipelineFlags_;
918
bool depthStencil = (curPipelineFlags_ & PipelineFlags::USES_DEPTH_STENCIL) != 0;
919
RenderPassType rpType = depthStencil ? RenderPassType::HAS_DEPTH : RenderPassType::DEFAULT;
920
921
if (curRenderStep_->render.framebuffer && (rpType & RenderPassType::HAS_DEPTH) && !curRenderStep_->render.framebuffer->HasDepth()) {
922
WARN_LOG(Log::G3D, "Trying to render with a depth-writing pipeline to a framebuffer without depth: %s", curRenderStep_->render.framebuffer->Tag());
923
rpType = RenderPassType::DEFAULT;
924
}
925
926
if (!curRenderStep_->render.framebuffer) {
927
rpType = RenderPassType::BACKBUFFER;
928
} else {
929
// Framebuffers can be stereo, and if so, will control the render pass type to match.
930
// Pipelines can be mono and render fine to stereo etc, so not checking them here.
931
// Note that we don't support rendering to just one layer of a multilayer framebuffer!
932
if (curRenderStep_->render.framebuffer->numLayers > 1) {
933
rpType = (RenderPassType)(rpType | RenderPassType::MULTIVIEW);
934
}
935
936
if (curRenderStep_->render.framebuffer->sampleCount != VK_SAMPLE_COUNT_1_BIT) {
937
rpType = (RenderPassType)(rpType | RenderPassType::MULTISAMPLE);
938
}
939
}
940
941
VKRRenderPass *renderPass = queueRunner_.GetRenderPass(key);
942
curRenderStep_->render.renderPassType = rpType;
943
944
VkSampleCountFlagBits sampleCount = curRenderStep_->render.framebuffer ? curRenderStep_->render.framebuffer->sampleCount : VK_SAMPLE_COUNT_1_BIT;
945
946
bool needsCompile = false;
947
for (VKRGraphicsPipeline *pipeline : pipelinesToCheck_) {
948
if (!pipeline) {
949
// Not good, but let's try not to crash.
950
continue;
951
}
952
std::unique_lock<std::mutex> lock(pipeline->mutex_);
953
if (!pipeline->pipeline[(size_t)rpType]) {
954
pipeline->pipeline[(size_t)rpType] = Promise<VkPipeline>::CreateEmpty();
955
lock.unlock();
956
957
_assert_(renderPass);
958
compileQueueMutex_.lock();
959
compileQueue_.emplace_back(pipeline, renderPass->Get(vulkan_, rpType, sampleCount), rpType, sampleCount);
960
compileQueueMutex_.unlock();
961
needsCompile = true;
962
}
963
}
964
965
compileQueueMutex_.lock();
966
if (needsCompile)
967
compileCond_.notify_one();
968
compileQueueMutex_.unlock();
969
pipelinesToCheck_.clear();
970
971
// We don't do this optimization for very small targets, probably not worth it.
972
if (!curRenderArea_.Empty() && (curWidth_ > 32 && curHeight_ > 32)) {
973
curRenderStep_->render.renderArea = curRenderArea_.ToVkRect2D();
974
} else {
975
curRenderStep_->render.renderArea.offset = {};
976
curRenderStep_->render.renderArea.extent = { (uint32_t)curWidth_, (uint32_t)curHeight_ };
977
}
978
curRenderArea_.Reset();
979
980
// We no longer have a current render step.
981
curRenderStep_ = nullptr;
982
curPipelineFlags_ = (PipelineFlags)0;
983
}
984
985
void VulkanRenderManager::BindFramebufferAsRenderTarget(VKRFramebuffer *fb, VKRRenderPassLoadAction color, VKRRenderPassLoadAction depth, VKRRenderPassLoadAction stencil, uint32_t clearColor, float clearDepth, uint8_t clearStencil, const char *tag) {
986
_dbg_assert_(insideFrame_);
987
988
// Eliminate dupes (bind of the framebuffer we already are rendering to), instantly convert to a clear if possible.
989
if (!steps_.empty() && steps_.back()->stepType == VKRStepType::RENDER && steps_.back()->render.framebuffer == fb) {
990
u32 clearMask = 0;
991
if (color == VKRRenderPassLoadAction::CLEAR) {
992
clearMask |= VK_IMAGE_ASPECT_COLOR_BIT;
993
}
994
if (depth == VKRRenderPassLoadAction::CLEAR) {
995
clearMask |= VK_IMAGE_ASPECT_DEPTH_BIT;
996
curPipelineFlags_ |= PipelineFlags::USES_DEPTH_STENCIL;
997
}
998
if (stencil == VKRRenderPassLoadAction::CLEAR) {
999
clearMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
1000
curPipelineFlags_ |= PipelineFlags::USES_DEPTH_STENCIL;
1001
}
1002
1003
// If we need a clear and the previous step has commands already, it's best to just add a clear and keep going.
1004
// If there's no clear needed, let's also do that.
1005
//
1006
// However, if we do need a clear and there are no commands in the previous pass,
1007
// we want the queuerunner to have the opportunity to merge, so we'll go ahead and make a new renderpass.
1008
if (clearMask == 0 || !steps_.back()->commands.empty()) {
1009
curRenderStep_ = steps_.back();
1010
curStepHasViewport_ = false;
1011
curStepHasScissor_ = false;
1012
for (const auto &c : steps_.back()->commands) {
1013
if (c.cmd == VKRRenderCommand::VIEWPORT) {
1014
curStepHasViewport_ = true;
1015
} else if (c.cmd == VKRRenderCommand::SCISSOR) {
1016
curStepHasScissor_ = true;
1017
}
1018
}
1019
if (clearMask != 0) {
1020
VkRenderData data{ VKRRenderCommand::CLEAR };
1021
data.clear.clearColor = clearColor;
1022
data.clear.clearZ = clearDepth;
1023
data.clear.clearStencil = clearStencil;
1024
data.clear.clearMask = clearMask;
1025
curRenderStep_->commands.push_back(data);
1026
curRenderArea_.SetRect(0, 0, curWidth_, curHeight_);
1027
}
1028
return;
1029
}
1030
}
1031
1032
#ifdef _DEBUG
1033
SanityCheckPassesOnAdd();
1034
#endif
1035
1036
// More redundant bind elimination.
1037
if (curRenderStep_) {
1038
if (curRenderStep_->commands.empty()) {
1039
if (curRenderStep_->render.colorLoad != VKRRenderPassLoadAction::CLEAR && curRenderStep_->render.depthLoad != VKRRenderPassLoadAction::CLEAR && curRenderStep_->render.stencilLoad != VKRRenderPassLoadAction::CLEAR) {
1040
// Can trivially kill the last empty render step.
1041
_dbg_assert_(steps_.back() == curRenderStep_);
1042
delete steps_.back();
1043
steps_.pop_back();
1044
curRenderStep_ = nullptr;
1045
}
1046
VLOG("Empty render step. Usually happens after uploading pixels..");
1047
}
1048
1049
EndCurRenderStep();
1050
}
1051
1052
// Sanity check that we don't have binds to the backbuffer before binds to other buffers. It must always be bound last.
1053
if (steps_.size() >= 1 && steps_.back()->stepType == VKRStepType::RENDER && steps_.back()->render.framebuffer == nullptr && fb != nullptr) {
1054
_dbg_assert_(false);
1055
}
1056
1057
// Older Mali drivers have issues with depth and stencil don't match load/clear/etc.
1058
// TODO: Determine which versions and do this only where necessary.
1059
u32 lateClearMask = 0;
1060
if (depth != stencil && vulkan_->GetPhysicalDeviceProperties().properties.vendorID == VULKAN_VENDOR_ARM) {
1061
if (stencil == VKRRenderPassLoadAction::DONT_CARE) {
1062
stencil = depth;
1063
} else if (depth == VKRRenderPassLoadAction::DONT_CARE) {
1064
depth = stencil;
1065
} else if (stencil == VKRRenderPassLoadAction::CLEAR) {
1066
depth = stencil;
1067
lateClearMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
1068
} else if (depth == VKRRenderPassLoadAction::CLEAR) {
1069
stencil = depth;
1070
lateClearMask |= VK_IMAGE_ASPECT_DEPTH_BIT;
1071
}
1072
}
1073
1074
VKRStep *step = new VKRStep{ VKRStepType::RENDER };
1075
step->render.framebuffer = fb;
1076
step->render.colorLoad = color;
1077
step->render.depthLoad = depth;
1078
step->render.stencilLoad = stencil;
1079
step->render.colorStore = VKRRenderPassStoreAction::STORE;
1080
step->render.depthStore = VKRRenderPassStoreAction::STORE;
1081
step->render.stencilStore = VKRRenderPassStoreAction::STORE;
1082
step->render.clearColor = clearColor;
1083
step->render.clearDepth = clearDepth;
1084
step->render.clearStencil = clearStencil;
1085
step->render.numDraws = 0;
1086
step->render.numReads = 0;
1087
step->render.finalColorLayout = !fb ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_UNDEFINED;
1088
step->render.finalDepthStencilLayout = !fb ? VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_UNDEFINED;
1089
// pipelineFlags, renderArea and renderPassType get filled in when we finalize the step. Do not read from them before that.
1090
step->tag = tag;
1091
steps_.push_back(step);
1092
1093
if (fb) {
1094
// If there's a KEEP, we naturally read from the framebuffer.
1095
if (color == VKRRenderPassLoadAction::KEEP || depth == VKRRenderPassLoadAction::KEEP || stencil == VKRRenderPassLoadAction::KEEP) {
1096
step->dependencies.insert(fb);
1097
}
1098
}
1099
1100
curRenderStep_ = step;
1101
curStepHasViewport_ = false;
1102
curStepHasScissor_ = false;
1103
if (fb) {
1104
curWidthRaw_ = fb->width;
1105
curHeightRaw_ = fb->height;
1106
curWidth_ = fb->width;
1107
curHeight_ = fb->height;
1108
} else {
1109
curWidthRaw_ = vulkan_->GetBackbufferWidth();
1110
curHeightRaw_ = vulkan_->GetBackbufferHeight();
1111
if (g_display.rotation == DisplayRotation::ROTATE_90 ||
1112
g_display.rotation == DisplayRotation::ROTATE_270) {
1113
curWidth_ = curHeightRaw_;
1114
curHeight_ = curWidthRaw_;
1115
} else {
1116
curWidth_ = curWidthRaw_;
1117
curHeight_ = curHeightRaw_;
1118
}
1119
}
1120
1121
if (color == VKRRenderPassLoadAction::CLEAR || depth == VKRRenderPassLoadAction::CLEAR || stencil == VKRRenderPassLoadAction::CLEAR) {
1122
curRenderArea_.SetRect(0, 0, curWidth_, curHeight_);
1123
}
1124
1125
// See above - we add a clear afterward if only one side for depth/stencil CLEAR/KEEP.
1126
if (lateClearMask != 0) {
1127
VkRenderData data{ VKRRenderCommand::CLEAR };
1128
data.clear.clearColor = clearColor;
1129
data.clear.clearZ = clearDepth;
1130
data.clear.clearStencil = clearStencil;
1131
data.clear.clearMask = lateClearMask;
1132
curRenderStep_->commands.push_back(data);
1133
}
1134
1135
if (invalidationCallback_) {
1136
invalidationCallback_(InvalidationCallbackFlags::RENDER_PASS_STATE);
1137
}
1138
}
1139
1140
bool VulkanRenderManager::CopyFramebufferToMemory(VKRFramebuffer *src, VkImageAspectFlags aspectBits, int x, int y, int w, int h, Draw::DataFormat destFormat, uint8_t *pixels, int pixelStride, Draw::ReadbackMode mode, const char *tag) {
1141
_dbg_assert_(insideFrame_);
1142
1143
for (int i = (int)steps_.size() - 1; i >= 0; i--) {
1144
if (steps_[i]->stepType == VKRStepType::RENDER && steps_[i]->render.framebuffer == src) {
1145
steps_[i]->render.numReads++;
1146
break;
1147
}
1148
}
1149
1150
EndCurRenderStep();
1151
1152
VKRStep *step = new VKRStep{ VKRStepType::READBACK };
1153
step->readback.aspectMask = aspectBits;
1154
step->readback.src = src;
1155
step->readback.srcRect.offset = { x, y };
1156
step->readback.srcRect.extent = { (uint32_t)w, (uint32_t)h };
1157
step->readback.delayed = mode == Draw::ReadbackMode::OLD_DATA_OK;
1158
step->dependencies.insert(src);
1159
step->tag = tag;
1160
steps_.push_back(step);
1161
1162
if (mode == Draw::ReadbackMode::BLOCK) {
1163
FlushSync();
1164
}
1165
1166
Draw::DataFormat srcFormat = Draw::DataFormat::UNDEFINED;
1167
if (aspectBits & VK_IMAGE_ASPECT_COLOR_BIT) {
1168
if (src) {
1169
switch (src->color.format) {
1170
case VK_FORMAT_R8G8B8A8_UNORM: srcFormat = Draw::DataFormat::R8G8B8A8_UNORM; break;
1171
default: _assert_(false);
1172
}
1173
} else {
1174
// Backbuffer.
1175
if (!(vulkan_->GetSurfaceCapabilities().supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_SRC_BIT)) {
1176
ERROR_LOG(Log::G3D, "Copying from backbuffer not supported, can't take screenshots");
1177
return false;
1178
}
1179
switch (vulkan_->GetSwapchainFormat()) {
1180
case VK_FORMAT_B8G8R8A8_UNORM: srcFormat = Draw::DataFormat::B8G8R8A8_UNORM; break;
1181
case VK_FORMAT_R8G8B8A8_UNORM: srcFormat = Draw::DataFormat::R8G8B8A8_UNORM; break;
1182
// NOTE: If you add supported formats here, make sure to also support them in VulkanQueueRunner::CopyReadbackBuffer.
1183
default:
1184
ERROR_LOG(Log::G3D, "Unsupported backbuffer format for screenshots");
1185
return false;
1186
}
1187
}
1188
} else if (aspectBits & VK_IMAGE_ASPECT_STENCIL_BIT) {
1189
// Copies from stencil are always S8.
1190
srcFormat = Draw::DataFormat::S8;
1191
} else if (aspectBits & VK_IMAGE_ASPECT_DEPTH_BIT) {
1192
switch (src->depth.format) {
1193
case VK_FORMAT_D24_UNORM_S8_UINT: srcFormat = Draw::DataFormat::D24_S8; break;
1194
case VK_FORMAT_D32_SFLOAT_S8_UINT: srcFormat = Draw::DataFormat::D32F; break;
1195
case VK_FORMAT_D16_UNORM_S8_UINT: srcFormat = Draw::DataFormat::D16; break;
1196
default: _assert_(false);
1197
}
1198
} else {
1199
_assert_(false);
1200
}
1201
1202
// Need to call this after FlushSync so the pixels are guaranteed to be ready in CPU-accessible VRAM.
1203
return queueRunner_.CopyReadbackBuffer(frameData_[vulkan_->GetCurFrame()],
1204
mode == Draw::ReadbackMode::OLD_DATA_OK ? src : nullptr, w, h, srcFormat, destFormat, pixelStride, pixels);
1205
}
1206
1207
void VulkanRenderManager::CopyImageToMemorySync(VkImage image, int mipLevel, int x, int y, int w, int h, Draw::DataFormat destFormat, uint8_t *pixels, int pixelStride, const char *tag) {
1208
_dbg_assert_(insideFrame_);
1209
1210
EndCurRenderStep();
1211
1212
VKRStep *step = new VKRStep{ VKRStepType::READBACK_IMAGE };
1213
step->readback_image.image = image;
1214
step->readback_image.srcRect.offset = { x, y };
1215
step->readback_image.srcRect.extent = { (uint32_t)w, (uint32_t)h };
1216
step->readback_image.mipLevel = mipLevel;
1217
step->tag = tag;
1218
steps_.push_back(step);
1219
1220
FlushSync();
1221
1222
// Need to call this after FlushSync so the pixels are guaranteed to be ready in CPU-accessible VRAM.
1223
queueRunner_.CopyReadbackBuffer(frameData_[vulkan_->GetCurFrame()], nullptr, w, h, destFormat, destFormat, pixelStride, pixels);
1224
1225
_dbg_assert_(steps_.empty());
1226
}
1227
1228
static void RemoveDrawCommands(FastVec<VkRenderData> *cmds) {
1229
// Here we remove any DRAW type commands when we hit a CLEAR.
1230
for (auto &c : *cmds) {
1231
if (c.cmd == VKRRenderCommand::DRAW || c.cmd == VKRRenderCommand::DRAW_INDEXED) {
1232
c.cmd = VKRRenderCommand::REMOVED;
1233
}
1234
}
1235
}
1236
1237
static void CleanupRenderCommands(FastVec<VkRenderData> *cmds) {
1238
size_t lastCommand[(int)VKRRenderCommand::NUM_RENDER_COMMANDS];
1239
memset(lastCommand, -1, sizeof(lastCommand));
1240
1241
// Find any duplicate state commands (likely from RemoveDrawCommands.)
1242
for (size_t i = 0; i < cmds->size(); ++i) {
1243
auto &c = cmds->at(i);
1244
auto &lastOfCmd = lastCommand[(uint8_t)c.cmd];
1245
1246
switch (c.cmd) {
1247
case VKRRenderCommand::REMOVED:
1248
continue;
1249
1250
case VKRRenderCommand::VIEWPORT:
1251
case VKRRenderCommand::SCISSOR:
1252
case VKRRenderCommand::BLEND:
1253
case VKRRenderCommand::STENCIL:
1254
if (lastOfCmd != -1) {
1255
cmds->at(lastOfCmd).cmd = VKRRenderCommand::REMOVED;
1256
}
1257
break;
1258
1259
case VKRRenderCommand::PUSH_CONSTANTS:
1260
// TODO: For now, we have to keep this one (it has an offset.) Still update lastCommand.
1261
break;
1262
1263
case VKRRenderCommand::CLEAR:
1264
// Ignore, doesn't participate in state.
1265
continue;
1266
1267
case VKRRenderCommand::DRAW_INDEXED:
1268
case VKRRenderCommand::DRAW:
1269
default:
1270
// Boundary - must keep state before this.
1271
memset(lastCommand, -1, sizeof(lastCommand));
1272
continue;
1273
}
1274
1275
lastOfCmd = i;
1276
}
1277
1278
// At this point, anything in lastCommand can be cleaned up too.
1279
// Note that it's safe to remove the last unused PUSH_CONSTANTS here.
1280
for (size_t i = 0; i < ARRAY_SIZE(lastCommand); ++i) {
1281
auto &lastOfCmd = lastCommand[i];
1282
if (lastOfCmd != -1) {
1283
cmds->at(lastOfCmd).cmd = VKRRenderCommand::REMOVED;
1284
}
1285
}
1286
}
1287
1288
void VulkanRenderManager::Clear(uint32_t clearColor, float clearZ, int clearStencil, int clearMask) {
1289
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == VKRStepType::RENDER);
1290
if (!clearMask)
1291
return;
1292
1293
// If this is the first drawing command or clears everything, merge it into the pass.
1294
int allAspects = VK_IMAGE_ASPECT_COLOR_BIT | VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
1295
if (curRenderStep_->render.numDraws == 0 || clearMask == allAspects) {
1296
curRenderStep_->render.clearColor = clearColor;
1297
curRenderStep_->render.clearDepth = clearZ;
1298
curRenderStep_->render.clearStencil = clearStencil;
1299
curRenderStep_->render.colorLoad = (clearMask & VK_IMAGE_ASPECT_COLOR_BIT) ? VKRRenderPassLoadAction::CLEAR : VKRRenderPassLoadAction::KEEP;
1300
curRenderStep_->render.depthLoad = (clearMask & VK_IMAGE_ASPECT_DEPTH_BIT) ? VKRRenderPassLoadAction::CLEAR : VKRRenderPassLoadAction::KEEP;
1301
curRenderStep_->render.stencilLoad = (clearMask & VK_IMAGE_ASPECT_STENCIL_BIT) ? VKRRenderPassLoadAction::CLEAR : VKRRenderPassLoadAction::KEEP;
1302
1303
if (clearMask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) {
1304
if (curRenderStep_->render.framebuffer && !curRenderStep_->render.framebuffer->HasDepth()) {
1305
WARN_LOG(Log::G3D, "Trying to clear depth/stencil on a non-depth framebuffer: %s", curRenderStep_->render.framebuffer->Tag());
1306
} else {
1307
curPipelineFlags_ |= PipelineFlags::USES_DEPTH_STENCIL;
1308
}
1309
}
1310
1311
// In case there were commands already.
1312
curRenderStep_->render.numDraws = 0;
1313
RemoveDrawCommands(&curRenderStep_->commands);
1314
} else {
1315
VkRenderData data{ VKRRenderCommand::CLEAR };
1316
data.clear.clearColor = clearColor;
1317
data.clear.clearZ = clearZ;
1318
data.clear.clearStencil = clearStencil;
1319
data.clear.clearMask = clearMask;
1320
curRenderStep_->commands.push_back(data);
1321
}
1322
1323
curRenderArea_.SetRect(0, 0, curWidth_, curHeight_);
1324
}
1325
1326
void VulkanRenderManager::CopyFramebuffer(VKRFramebuffer *src, VkRect2D srcRect, VKRFramebuffer *dst, VkOffset2D dstPos, VkImageAspectFlags aspectMask, const char *tag) {
1327
#ifdef _DEBUG
1328
SanityCheckPassesOnAdd();
1329
#endif
1330
// _dbg_assert_msg_(src != dst, "Can't copy within the same buffer");
1331
if (src == dst) {
1332
// TODO: Check for rectangle self-overlap.
1333
}
1334
1335
_dbg_assert_msg_(srcRect.offset.x >= 0, "srcrect offset x (%d) < 0", srcRect.offset.x);
1336
_dbg_assert_msg_(srcRect.offset.y >= 0, "srcrect offset y (%d) < 0", srcRect.offset.y);
1337
_dbg_assert_msg_(srcRect.offset.x + srcRect.extent.width <= (uint32_t)src->width, "srcrect offset x (%d) + extent (%d) > width (%d)", srcRect.offset.x, srcRect.extent.width, (uint32_t)src->width);
1338
_dbg_assert_msg_(srcRect.offset.y + srcRect.extent.height <= (uint32_t)src->height, "srcrect offset y (%d) + extent (%d) > height (%d)", srcRect.offset.y, srcRect.extent.height, (uint32_t)src->height);
1339
1340
_dbg_assert_msg_(srcRect.extent.width > 0, "copy srcwidth == 0");
1341
_dbg_assert_msg_(srcRect.extent.height > 0, "copy srcheight == 0");
1342
1343
_dbg_assert_msg_(dstPos.x >= 0, "dstPos offset x (%d) < 0", dstPos.x);
1344
_dbg_assert_msg_(dstPos.y >= 0, "dstPos offset y (%d) < 0", dstPos.y);
1345
_dbg_assert_msg_(dstPos.x + srcRect.extent.width <= (uint32_t)dst->width, "dstPos + extent x > width");
1346
_dbg_assert_msg_(dstPos.y + srcRect.extent.height <= (uint32_t)dst->height, "dstPos + extent y > height");
1347
1348
VkImageLayout finalSrcLayoutBeforeCopy = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
1349
if (src == dst) {
1350
// We only use the first loop before, and transition to VK_IMAGE_LAYOUT_GENERAL.
1351
finalSrcLayoutBeforeCopy = VK_IMAGE_LAYOUT_GENERAL;
1352
}
1353
1354
for (int i = (int)steps_.size() - 1; i >= 0; i--) {
1355
if (steps_[i]->stepType == VKRStepType::RENDER && steps_[i]->render.framebuffer == src) {
1356
if (aspectMask & VK_IMAGE_ASPECT_COLOR_BIT) {
1357
if (steps_[i]->render.finalColorLayout == VK_IMAGE_LAYOUT_UNDEFINED) {
1358
steps_[i]->render.finalColorLayout = finalSrcLayoutBeforeCopy;
1359
}
1360
}
1361
if (aspectMask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) {
1362
if (steps_[i]->render.finalDepthStencilLayout == VK_IMAGE_LAYOUT_UNDEFINED) {
1363
steps_[i]->render.finalDepthStencilLayout = finalSrcLayoutBeforeCopy;
1364
}
1365
}
1366
steps_[i]->render.numReads++;
1367
break;
1368
}
1369
}
1370
1371
if (src != dst) {
1372
for (int i = (int)steps_.size() - 1; i >= 0; i--) {
1373
if (steps_[i]->stepType == VKRStepType::RENDER && steps_[i]->render.framebuffer == dst) {
1374
if (aspectMask & VK_IMAGE_ASPECT_COLOR_BIT) {
1375
if (steps_[i]->render.finalColorLayout == VK_IMAGE_LAYOUT_UNDEFINED) {
1376
steps_[i]->render.finalColorLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
1377
}
1378
}
1379
if (aspectMask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) {
1380
if (steps_[i]->render.finalDepthStencilLayout == VK_IMAGE_LAYOUT_UNDEFINED) {
1381
steps_[i]->render.finalDepthStencilLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
1382
}
1383
}
1384
break;
1385
}
1386
}
1387
}
1388
1389
EndCurRenderStep();
1390
1391
VKRStep *step = new VKRStep{ VKRStepType::COPY };
1392
1393
step->copy.aspectMask = aspectMask;
1394
step->copy.src = src;
1395
step->copy.srcRect = srcRect;
1396
step->copy.dst = dst;
1397
step->copy.dstPos = dstPos;
1398
step->dependencies.insert(src);
1399
step->tag = tag;
1400
bool fillsDst = dst && srcRect.offset.x == 0 && srcRect.offset.y == 0 && srcRect.extent.width == dst->width && srcRect.extent.height == dst->height;
1401
if (dstPos.x != 0 || dstPos.y != 0 || !fillsDst)
1402
step->dependencies.insert(dst);
1403
1404
steps_.push_back(step);
1405
}
1406
1407
void VulkanRenderManager::BlitFramebuffer(VKRFramebuffer *src, VkRect2D srcRect, VKRFramebuffer *dst, VkRect2D dstRect, VkImageAspectFlags aspectMask, VkFilter filter, const char *tag) {
1408
#ifdef _DEBUG
1409
SanityCheckPassesOnAdd();
1410
#endif
1411
1412
_dbg_assert_msg_(srcRect.offset.x >= 0, "srcrect offset x (%d) < 0", srcRect.offset.x);
1413
_dbg_assert_msg_(srcRect.offset.y >= 0, "srcrect offset y (%d) < 0", srcRect.offset.y);
1414
_dbg_assert_msg_(srcRect.offset.x + srcRect.extent.width <= (uint32_t)src->width, "srcrect offset x (%d) + extent (%d) > width (%d)", srcRect.offset.x, srcRect.extent.width, (uint32_t)src->width);
1415
_dbg_assert_msg_(srcRect.offset.y + srcRect.extent.height <= (uint32_t)src->height, "srcrect offset y (%d) + extent (%d) > height (%d)", srcRect.offset.y, srcRect.extent.height, (uint32_t)src->height);
1416
1417
_dbg_assert_msg_(srcRect.extent.width > 0, "blit srcwidth == 0");
1418
_dbg_assert_msg_(srcRect.extent.height > 0, "blit srcheight == 0");
1419
1420
_dbg_assert_msg_(dstRect.offset.x >= 0, "dstrect offset x < 0");
1421
_dbg_assert_msg_(dstRect.offset.y >= 0, "dstrect offset y < 0");
1422
_dbg_assert_msg_(dstRect.offset.x + dstRect.extent.width <= (uint32_t)dst->width, "dstrect offset x + extent > width");
1423
_dbg_assert_msg_(dstRect.offset.y + dstRect.extent.height <= (uint32_t)dst->height, "dstrect offset y + extent > height");
1424
1425
_dbg_assert_msg_(dstRect.extent.width > 0, "blit dstwidth == 0");
1426
_dbg_assert_msg_(dstRect.extent.height > 0, "blit dstheight == 0");
1427
1428
// TODO: Seem to be missing final layouts here like in Copy...
1429
1430
for (int i = (int)steps_.size() - 1; i >= 0; i--) {
1431
if (steps_[i]->stepType == VKRStepType::RENDER && steps_[i]->render.framebuffer == src) {
1432
steps_[i]->render.numReads++;
1433
break;
1434
}
1435
}
1436
1437
// Sanity check. Added an assert to try to gather more info.
1438
// Got this assert in NPJH50443 FINAL FANTASY TYPE-0, but pretty rare. Moving back to debug assert.
1439
if (aspectMask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) {
1440
_dbg_assert_msg_(src->depth.image != VK_NULL_HANDLE, "%s", src->Tag());
1441
_dbg_assert_msg_(dst->depth.image != VK_NULL_HANDLE, "%s", dst->Tag());
1442
1443
if (!src->depth.image || !dst->depth.image) {
1444
// Something has gone wrong, but let's try to stumble along.
1445
return;
1446
}
1447
}
1448
1449
EndCurRenderStep();
1450
1451
VKRStep *step = new VKRStep{ VKRStepType::BLIT };
1452
step->blit.aspectMask = aspectMask;
1453
step->blit.src = src;
1454
step->blit.srcRect = srcRect;
1455
step->blit.dst = dst;
1456
step->blit.dstRect = dstRect;
1457
step->blit.filter = filter;
1458
step->dependencies.insert(src);
1459
step->tag = tag;
1460
bool fillsDst = dst && dstRect.offset.x == 0 && dstRect.offset.y == 0 && dstRect.extent.width == dst->width && dstRect.extent.height == dst->height;
1461
if (!fillsDst)
1462
step->dependencies.insert(dst);
1463
1464
steps_.push_back(step);
1465
}
1466
1467
VkImageView VulkanRenderManager::BindFramebufferAsTexture(VKRFramebuffer *fb, int binding, VkImageAspectFlags aspectBit, int layer) {
1468
_dbg_assert_(curRenderStep_ != nullptr);
1469
_dbg_assert_(fb != nullptr);
1470
1471
// We don't support texturing from stencil, neither do we support texturing from depth|stencil together (nonsensical).
1472
_dbg_assert_(aspectBit == VK_IMAGE_ASPECT_COLOR_BIT || aspectBit == VK_IMAGE_ASPECT_DEPTH_BIT);
1473
1474
// Mark the dependency, check for required transitions, and return the image.
1475
1476
// Optimization: If possible, use final*Layout to put the texture into the correct layout "early".
1477
for (int i = (int)steps_.size() - 1; i >= 0; i--) {
1478
if (steps_[i]->stepType == VKRStepType::RENDER && steps_[i]->render.framebuffer == fb) {
1479
if (aspectBit == VK_IMAGE_ASPECT_COLOR_BIT) {
1480
// If this framebuffer was rendered to earlier in this frame, make sure to pre-transition it to the correct layout.
1481
if (steps_[i]->render.finalColorLayout == VK_IMAGE_LAYOUT_UNDEFINED) {
1482
steps_[i]->render.finalColorLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1483
}
1484
// If we find some other layout, a copy after this is likely involved. It's fine though,
1485
// we'll just transition it right as we need it and lose a tiny optimization.
1486
} else if (aspectBit == VK_IMAGE_ASPECT_DEPTH_BIT) {
1487
// If this framebuffer was rendered to earlier in this frame, make sure to pre-transition it to the correct layout.
1488
if (steps_[i]->render.finalDepthStencilLayout == VK_IMAGE_LAYOUT_UNDEFINED) {
1489
steps_[i]->render.finalDepthStencilLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1490
}
1491
} // We don't (yet?) support texturing from stencil images.
1492
steps_[i]->render.numReads++;
1493
break;
1494
}
1495
}
1496
1497
// Track dependencies fully.
1498
curRenderStep_->dependencies.insert(fb);
1499
1500
// Add this pretransition unless we already have it.
1501
TransitionRequest rq{ fb, aspectBit, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL };
1502
curRenderStep_->preTransitions.insert(rq); // Note that insert avoids inserting duplicates.
1503
1504
if (layer == -1) {
1505
return aspectBit == VK_IMAGE_ASPECT_COLOR_BIT ? fb->color.texAllLayersView : fb->depth.texAllLayersView;
1506
} else {
1507
return aspectBit == VK_IMAGE_ASPECT_COLOR_BIT ? fb->color.texLayerViews[layer] : fb->depth.texLayerViews[layer];
1508
}
1509
}
1510
1511
// Called on main thread.
1512
// Sends the collected commands to the render thread. Submit-latency should be
1513
// measured from here, probably.
1514
void VulkanRenderManager::Finish() {
1515
EndCurRenderStep();
1516
1517
// Let's do just a bit of cleanup on render commands now.
1518
// TODO: Should look into removing this.
1519
for (auto &step : steps_) {
1520
if (step->stepType == VKRStepType::RENDER) {
1521
CleanupRenderCommands(&step->commands);
1522
}
1523
}
1524
1525
int curFrame = vulkan_->GetCurFrame();
1526
FrameData &frameData = frameData_[curFrame];
1527
1528
if (!postInitBarrier_.empty()) {
1529
VkCommandBuffer buffer = frameData.GetInitCmd(vulkan_);
1530
postInitBarrier_.Flush(buffer);
1531
}
1532
1533
VLOG("PUSH: Frame[%d]", curFrame);
1534
VKRRenderThreadTask *task = new VKRRenderThreadTask(VKRRunType::SUBMIT);
1535
task->frame = curFrame;
1536
if (useRenderThread_) {
1537
std::unique_lock<std::mutex> lock(pushMutex_);
1538
renderThreadQueue_.push(task);
1539
renderThreadQueue_.back()->steps = std::move(steps_);
1540
pushCondVar_.notify_one();
1541
} else {
1542
// Just do it!
1543
task->steps = std::move(steps_);
1544
Run(*task);
1545
delete task;
1546
}
1547
1548
steps_.clear();
1549
}
1550
1551
void VulkanRenderManager::Present() {
1552
int curFrame = vulkan_->GetCurFrame();
1553
1554
VKRRenderThreadTask *task = new VKRRenderThreadTask(VKRRunType::PRESENT);
1555
task->frame = curFrame;
1556
if (useRenderThread_) {
1557
std::unique_lock<std::mutex> lock(pushMutex_);
1558
renderThreadQueue_.push(task);
1559
pushCondVar_.notify_one();
1560
} else {
1561
// Just do it!
1562
Run(*task);
1563
delete task;
1564
}
1565
1566
vulkan_->EndFrame();
1567
insideFrame_ = false;
1568
}
1569
1570
// Called on the render thread.
1571
//
1572
// Can be called again after a VKRRunType::SYNC on the same frame.
1573
void VulkanRenderManager::Run(VKRRenderThreadTask &task) {
1574
FrameData &frameData = frameData_[task.frame];
1575
1576
if (task.runType == VKRRunType::PRESENT) {
1577
if (!frameData.skipSwap) {
1578
VkResult res = frameData.QueuePresent(vulkan_, frameDataShared_);
1579
frameTimeHistory_[frameData.frameId].queuePresent = time_now_d();
1580
if (res == VK_ERROR_OUT_OF_DATE_KHR) {
1581
// We clearly didn't get this in vkAcquireNextImageKHR because of the skipSwap check above.
1582
// Do the increment.
1583
outOfDateFrames_++;
1584
} else if (res == VK_SUBOPTIMAL_KHR) {
1585
outOfDateFrames_++;
1586
} else if (res != VK_SUCCESS) {
1587
_assert_msg_(false, "vkQueuePresentKHR failed! result=%s", VulkanResultToString(res));
1588
} else {
1589
// Success
1590
outOfDateFrames_ = 0;
1591
}
1592
} else {
1593
// We only get here if vkAcquireNextImage returned VK_ERROR_OUT_OF_DATE.
1594
if (vulkan_->HasRealSwapchain()) {
1595
outOfDateFrames_++;
1596
}
1597
frameData.skipSwap = false;
1598
}
1599
return;
1600
}
1601
1602
_dbg_assert_(!frameData.hasPresentCommands);
1603
1604
if (!frameTimeHistory_[frameData.frameId].firstSubmit) {
1605
frameTimeHistory_[frameData.frameId].firstSubmit = time_now_d();
1606
}
1607
frameData.Submit(vulkan_, FrameSubmitType::Pending, frameDataShared_);
1608
1609
// Flush descriptors.
1610
double descStart = time_now_d();
1611
FlushDescriptors(task.frame);
1612
frameData.profile.descWriteTime = time_now_d() - descStart;
1613
1614
if (!frameData.hasMainCommands) {
1615
// Effectively resets both main and present command buffers, since they both live in this pool.
1616
// We always record main commands first, so we don't need to reset the present command buffer separately.
1617
vkResetCommandPool(vulkan_->GetDevice(), frameData.cmdPoolMain, 0);
1618
1619
VkCommandBufferBeginInfo begin{ VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO };
1620
begin.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
1621
VkResult res = vkBeginCommandBuffer(frameData.mainCmd, &begin);
1622
frameData.hasMainCommands = true;
1623
_assert_msg_(res == VK_SUCCESS, "vkBeginCommandBuffer failed! result=%s", VulkanResultToString(res));
1624
}
1625
1626
queueRunner_.PreprocessSteps(task.steps);
1627
// Likely during shutdown, happens in headless.
1628
if (task.steps.empty() && !frameData.hasAcquired)
1629
frameData.skipSwap = true;
1630
//queueRunner_.LogSteps(stepsOnThread, false);
1631
queueRunner_.RunSteps(task.steps, task.frame, frameData, frameDataShared_);
1632
1633
switch (task.runType) {
1634
case VKRRunType::SUBMIT:
1635
frameData.Submit(vulkan_, FrameSubmitType::FinishFrame, frameDataShared_);
1636
break;
1637
1638
case VKRRunType::SYNC:
1639
// The submit will trigger the readbackFence, and also do the wait for it.
1640
frameData.Submit(vulkan_, FrameSubmitType::Sync, frameDataShared_);
1641
1642
if (useRenderThread_) {
1643
std::unique_lock<std::mutex> lock(syncMutex_);
1644
syncCondVar_.notify_one();
1645
}
1646
1647
// At this point the GPU is idle, and we can resume filling the command buffers for the
1648
// current frame since and thus all previously enqueued command buffers have been
1649
// processed. No need to switch to the next frame number, would just be confusing.
1650
break;
1651
1652
default:
1653
_dbg_assert_(false);
1654
break;
1655
}
1656
1657
VLOG("PULL: Finished running frame %d", task.frame);
1658
}
1659
1660
// Called from main thread.
1661
void VulkanRenderManager::FlushSync() {
1662
_dbg_assert_(!curRenderStep_);
1663
1664
if (invalidationCallback_) {
1665
invalidationCallback_(InvalidationCallbackFlags::COMMAND_BUFFER_STATE);
1666
}
1667
1668
int curFrame = vulkan_->GetCurFrame();
1669
FrameData &frameData = frameData_[curFrame];
1670
1671
if (!postInitBarrier_.empty()) {
1672
VkCommandBuffer buffer = frameData.GetInitCmd(vulkan_);
1673
postInitBarrier_.Flush(buffer);
1674
}
1675
1676
if (useRenderThread_) {
1677
{
1678
VLOG("PUSH: Frame[%d]", curFrame);
1679
VKRRenderThreadTask *task = new VKRRenderThreadTask(VKRRunType::SYNC);
1680
task->frame = curFrame;
1681
{
1682
std::unique_lock<std::mutex> lock(pushMutex_);
1683
renderThreadQueue_.push(task);
1684
renderThreadQueue_.back()->steps = std::move(steps_);
1685
pushCondVar_.notify_one();
1686
}
1687
steps_.clear();
1688
}
1689
1690
{
1691
std::unique_lock<std::mutex> lock(syncMutex_);
1692
// Wait for the flush to be hit, since we're syncing.
1693
while (!frameData.syncDone) {
1694
VLOG("PUSH: Waiting for frame[%d].syncDone = 1 (sync)", curFrame);
1695
syncCondVar_.wait(lock);
1696
}
1697
frameData.syncDone = false;
1698
}
1699
} else {
1700
VKRRenderThreadTask task(VKRRunType::SYNC);
1701
task.frame = curFrame;
1702
task.steps = std::move(steps_);
1703
Run(task);
1704
steps_.clear();
1705
}
1706
}
1707
1708
void VulkanRenderManager::ResetStats() {
1709
initTimeMs_.Reset();
1710
totalGPUTimeMs_.Reset();
1711
renderCPUTimeMs_.Reset();
1712
}
1713
1714
VKRPipelineLayout *VulkanRenderManager::CreatePipelineLayout(BindingType *bindingTypes, size_t bindingTypesCount, bool geoShadersEnabled, const char *tag) {
1715
VKRPipelineLayout *layout = new VKRPipelineLayout();
1716
layout->SetTag(tag);
1717
layout->bindingTypesCount = (uint32_t)bindingTypesCount;
1718
1719
_dbg_assert_(bindingTypesCount <= ARRAY_SIZE(layout->bindingTypes));
1720
memcpy(layout->bindingTypes, bindingTypes, sizeof(BindingType) * bindingTypesCount);
1721
1722
VkDescriptorSetLayoutBinding bindings[VKRPipelineLayout::MAX_DESC_SET_BINDINGS];
1723
for (int i = 0; i < (int)bindingTypesCount; i++) {
1724
bindings[i].binding = i;
1725
bindings[i].descriptorCount = 1;
1726
bindings[i].pImmutableSamplers = nullptr;
1727
1728
switch (bindingTypes[i]) {
1729
case BindingType::COMBINED_IMAGE_SAMPLER:
1730
bindings[i].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
1731
bindings[i].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
1732
break;
1733
case BindingType::UNIFORM_BUFFER_DYNAMIC_VERTEX:
1734
bindings[i].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
1735
bindings[i].stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
1736
break;
1737
case BindingType::UNIFORM_BUFFER_DYNAMIC_ALL:
1738
bindings[i].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
1739
bindings[i].stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
1740
if (geoShadersEnabled) {
1741
bindings[i].stageFlags |= VK_SHADER_STAGE_GEOMETRY_BIT;
1742
}
1743
break;
1744
case BindingType::STORAGE_BUFFER_VERTEX:
1745
bindings[i].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
1746
bindings[i].stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
1747
break;
1748
case BindingType::STORAGE_BUFFER_COMPUTE:
1749
bindings[i].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
1750
bindings[i].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
1751
break;
1752
case BindingType::STORAGE_IMAGE_COMPUTE:
1753
bindings[i].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
1754
bindings[i].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
1755
break;
1756
default:
1757
UNREACHABLE();
1758
break;
1759
}
1760
}
1761
1762
VkDescriptorSetLayoutCreateInfo dsl = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO };
1763
dsl.bindingCount = (uint32_t)bindingTypesCount;
1764
dsl.pBindings = bindings;
1765
VkResult res = vkCreateDescriptorSetLayout(vulkan_->GetDevice(), &dsl, nullptr, &layout->descriptorSetLayout);
1766
_assert_(VK_SUCCESS == res && layout->descriptorSetLayout);
1767
1768
VkPipelineLayoutCreateInfo pl = { VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO };
1769
VkDescriptorSetLayout setLayouts[1] = { layout->descriptorSetLayout };
1770
pl.setLayoutCount = ARRAY_SIZE(setLayouts);
1771
pl.pSetLayouts = setLayouts;
1772
res = vkCreatePipelineLayout(vulkan_->GetDevice(), &pl, nullptr, &layout->pipelineLayout);
1773
_assert_(VK_SUCCESS == res && layout->pipelineLayout);
1774
1775
vulkan_->SetDebugName(layout->descriptorSetLayout, VK_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT, tag);
1776
vulkan_->SetDebugName(layout->pipelineLayout, VK_OBJECT_TYPE_PIPELINE_LAYOUT, tag);
1777
1778
for (int i = 0; i < VulkanContext::MAX_INFLIGHT_FRAMES; i++) {
1779
// Some games go beyond 1024 and end up having to resize like GTA, but most stay below so we start there.
1780
layout->frameData[i].pool.Create(vulkan_, bindingTypes, (uint32_t)bindingTypesCount, 1024);
1781
}
1782
1783
pipelineLayouts_.push_back(layout);
1784
return layout;
1785
}
1786
1787
void VulkanRenderManager::DestroyPipelineLayout(VKRPipelineLayout *layout) {
1788
for (auto iter = pipelineLayouts_.begin(); iter != pipelineLayouts_.end(); iter++) {
1789
if (*iter == layout) {
1790
pipelineLayouts_.erase(iter);
1791
break;
1792
}
1793
}
1794
vulkan_->Delete().QueueCallback([](VulkanContext *vulkan, void *userdata) {
1795
VKRPipelineLayout *layout = (VKRPipelineLayout *)userdata;
1796
for (int i = 0; i < VulkanContext::MAX_INFLIGHT_FRAMES; i++) {
1797
layout->frameData[i].pool.DestroyImmediately();
1798
}
1799
vkDestroyPipelineLayout(vulkan->GetDevice(), layout->pipelineLayout, nullptr);
1800
vkDestroyDescriptorSetLayout(vulkan->GetDevice(), layout->descriptorSetLayout, nullptr);
1801
1802
delete layout;
1803
}, layout);
1804
}
1805
1806
void VulkanRenderManager::FlushDescriptors(int frame) {
1807
for (auto iter : pipelineLayouts_) {
1808
iter->FlushDescSets(vulkan_, frame, &frameData_[frame].profile);
1809
}
1810
}
1811
1812
void VulkanRenderManager::ResetDescriptorLists(int frame) {
1813
for (auto iter : pipelineLayouts_) {
1814
VKRPipelineLayout::FrameData &data = iter->frameData[frame];
1815
1816
data.flushedDescriptors_ = 0;
1817
data.descSets_.clear();
1818
data.descData_.clear();
1819
}
1820
}
1821
1822
VKRPipelineLayout::~VKRPipelineLayout() {
1823
_assert_(frameData[0].pool.IsDestroyed());
1824
}
1825
1826
void VKRPipelineLayout::FlushDescSets(VulkanContext *vulkan, int frame, QueueProfileContext *profile) {
1827
_dbg_assert_(frame < VulkanContext::MAX_INFLIGHT_FRAMES);
1828
1829
FrameData &data = frameData[frame];
1830
1831
VulkanDescSetPool &pool = data.pool;
1832
FastVec<PackedDescriptor> &descData = data.descData_;
1833
FastVec<PendingDescSet> &descSets = data.descSets_;
1834
1835
pool.Reset();
1836
1837
VkDescriptorSet setCache[8];
1838
VkDescriptorSetLayout layoutsForAlloc[ARRAY_SIZE(setCache)];
1839
for (int i = 0; i < ARRAY_SIZE(setCache); i++) {
1840
layoutsForAlloc[i] = descriptorSetLayout;
1841
}
1842
int setsUsed = ARRAY_SIZE(setCache); // To allocate immediately.
1843
1844
// This will write all descriptors.
1845
// Initially, we just do a simple look-back comparing to the previous descriptor to avoid sequential dupes.
1846
// In theory, we could multithread this. Gotta be a lot of descriptors for that to be worth it though.
1847
1848
// Initially, let's do naive single desc set writes.
1849
VkWriteDescriptorSet writes[MAX_DESC_SET_BINDINGS];
1850
VkDescriptorImageInfo imageInfo[MAX_DESC_SET_BINDINGS]; // just picked a practical number
1851
VkDescriptorBufferInfo bufferInfo[MAX_DESC_SET_BINDINGS];
1852
1853
// Preinitialize fields that won't change.
1854
for (size_t i = 0; i < ARRAY_SIZE(writes); i++) {
1855
writes[i].descriptorCount = 1;
1856
writes[i].dstArrayElement = 0;
1857
writes[i].pTexelBufferView = nullptr;
1858
writes[i].pNext = nullptr;
1859
writes[i].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
1860
}
1861
1862
size_t start = data.flushedDescriptors_;
1863
int writeCount = 0, dedupCount = 0;
1864
1865
for (size_t index = start; index < descSets.size(); index++) {
1866
auto &d = descSets[index];
1867
1868
// This is where we look up to see if we already have an identical descriptor previously in the array.
1869
// We could do a simple custom hash map here that doesn't handle collisions, since those won't matter.
1870
// Instead, for now we just check history one item backwards. Good enough, it seems.
1871
if (index > start + 1) {
1872
if (descSets[index - 1].count == d.count) {
1873
if (!memcmp(descData.data() + d.offset, descData.data() + descSets[index - 1].offset, d.count * sizeof(PackedDescriptor))) {
1874
d.set = descSets[index - 1].set;
1875
dedupCount++;
1876
continue;
1877
}
1878
}
1879
}
1880
1881
if (setsUsed < ARRAY_SIZE(setCache)) {
1882
d.set = setCache[setsUsed++];
1883
} else {
1884
// Allocate in small batches.
1885
bool success = pool.Allocate(setCache, ARRAY_SIZE(setCache), layoutsForAlloc);
1886
_dbg_assert_(success);
1887
d.set = setCache[0];
1888
setsUsed = 1;
1889
}
1890
1891
// TODO: Build up bigger batches of writes.
1892
const PackedDescriptor *data = descData.begin() + d.offset;
1893
int numWrites = 0;
1894
int numBuffers = 0;
1895
int numImages = 0;
1896
for (int i = 0; i < d.count; i++) {
1897
if (!data[i].image.view) { // This automatically also checks for an null buffer due to the union.
1898
continue;
1899
}
1900
switch (this->bindingTypes[i]) {
1901
case BindingType::COMBINED_IMAGE_SAMPLER:
1902
_dbg_assert_(data[i].image.sampler != VK_NULL_HANDLE);
1903
_dbg_assert_(data[i].image.view != VK_NULL_HANDLE);
1904
imageInfo[numImages].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1905
imageInfo[numImages].imageView = data[i].image.view;
1906
imageInfo[numImages].sampler = data[i].image.sampler;
1907
writes[numWrites].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
1908
writes[numWrites].pImageInfo = &imageInfo[numImages];
1909
writes[numWrites].pBufferInfo = nullptr;
1910
numImages++;
1911
break;
1912
case BindingType::STORAGE_IMAGE_COMPUTE:
1913
_dbg_assert_(data[i].image.view != VK_NULL_HANDLE);
1914
imageInfo[numImages].imageLayout = VK_IMAGE_LAYOUT_GENERAL;
1915
imageInfo[numImages].imageView = data[i].image.view;
1916
imageInfo[numImages].sampler = VK_NULL_HANDLE;
1917
writes[numWrites].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
1918
writes[numWrites].pImageInfo = &imageInfo[numImages];
1919
writes[numWrites].pBufferInfo = nullptr;
1920
numImages++;
1921
break;
1922
case BindingType::STORAGE_BUFFER_VERTEX:
1923
case BindingType::STORAGE_BUFFER_COMPUTE:
1924
_dbg_assert_(data[i].buffer.buffer != VK_NULL_HANDLE);
1925
bufferInfo[numBuffers].buffer = data[i].buffer.buffer;
1926
bufferInfo[numBuffers].range = data[i].buffer.range;
1927
bufferInfo[numBuffers].offset = data[i].buffer.offset;
1928
writes[numWrites].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
1929
writes[numWrites].pBufferInfo = &bufferInfo[numBuffers];
1930
writes[numWrites].pImageInfo = nullptr;
1931
numBuffers++;
1932
break;
1933
case BindingType::UNIFORM_BUFFER_DYNAMIC_ALL:
1934
case BindingType::UNIFORM_BUFFER_DYNAMIC_VERTEX:
1935
_dbg_assert_(data[i].buffer.buffer != VK_NULL_HANDLE);
1936
bufferInfo[numBuffers].buffer = data[i].buffer.buffer;
1937
bufferInfo[numBuffers].range = data[i].buffer.range;
1938
bufferInfo[numBuffers].offset = 0;
1939
writes[numWrites].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
1940
writes[numWrites].pBufferInfo = &bufferInfo[numBuffers];
1941
writes[numWrites].pImageInfo = nullptr;
1942
numBuffers++;
1943
break;
1944
}
1945
writes[numWrites].dstBinding = i;
1946
writes[numWrites].dstSet = d.set;
1947
numWrites++;
1948
}
1949
1950
vkUpdateDescriptorSets(vulkan->GetDevice(), numWrites, writes, 0, nullptr);
1951
1952
writeCount++;
1953
}
1954
1955
data.flushedDescriptors_ = (int)descSets.size();
1956
profile->descriptorsWritten += writeCount;
1957
profile->descriptorsDeduped += dedupCount;
1958
}
1959
1960
void VulkanRenderManager::SanityCheckPassesOnAdd() {
1961
#if _DEBUG
1962
// Check that we don't have any previous passes that write to the backbuffer, that must ALWAYS be the last one.
1963
for (int i = 0; i < (int)steps_.size(); i++) {
1964
if (steps_[i]->stepType == VKRStepType::RENDER) {
1965
_dbg_assert_msg_(steps_[i]->render.framebuffer != nullptr, "Adding second backbuffer pass? Not good!");
1966
}
1967
}
1968
#endif
1969
}
1970
1971