Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
stenzek
GitHub Repository: stenzek/duckstation
Path: blob/master/src/util/gpu_device.cpp
4214 views
1
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <[email protected]>
2
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
3
4
#include "gpu_device.h"
5
#include "compress_helpers.h"
6
#include "gpu_framebuffer_manager.h"
7
#include "image.h"
8
#include "shadergen.h"
9
10
#include "common/assert.h"
11
#include "common/dynamic_library.h"
12
#include "common/error.h"
13
#include "common/file_system.h"
14
#include "common/log.h"
15
#include "common/path.h"
16
#include "common/scoped_guard.h"
17
#include "common/sha1_digest.h"
18
#include "common/string_util.h"
19
#include "common/timer.h"
20
21
#include "fmt/format.h"
22
#include "shaderc/shaderc.h"
23
#include "spirv_cross_c.h"
24
#include "xxhash.h"
25
26
LOG_CHANNEL(GPUDevice);
27
28
#ifdef _WIN32
29
#include "common/windows_headers.h"
30
#include "d3d11_device.h"
31
#include "d3d12_device.h"
32
#include "d3d_common.h"
33
#endif
34
35
#ifdef ENABLE_OPENGL
36
#include "opengl_device.h"
37
#endif
38
39
#ifdef ENABLE_VULKAN
40
#include "vulkan_device.h"
41
#endif
42
43
std::unique_ptr<GPUDevice> g_gpu_device;
44
45
static std::string s_shader_dump_path;
46
static std::string s_pipeline_cache_path;
47
static size_t s_pipeline_cache_size;
48
static std::array<u8, SHA1Digest::DIGEST_SIZE> s_pipeline_cache_hash;
49
size_t GPUDevice::s_total_vram_usage = 0;
50
GPUDevice::Statistics GPUDevice::s_stats = {};
51
52
GPUSampler::GPUSampler() = default;
53
54
GPUSampler::~GPUSampler() = default;
55
56
GPUSampler::Config GPUSampler::GetNearestConfig()
57
{
58
Config config = {};
59
config.address_u = GPUSampler::AddressMode::ClampToEdge;
60
config.address_v = GPUSampler::AddressMode::ClampToEdge;
61
config.address_w = GPUSampler::AddressMode::ClampToEdge;
62
config.min_filter = GPUSampler::Filter::Nearest;
63
config.mag_filter = GPUSampler::Filter::Nearest;
64
return config;
65
}
66
67
GPUSampler::Config GPUSampler::GetLinearConfig()
68
{
69
Config config = {};
70
config.address_u = GPUSampler::AddressMode::ClampToEdge;
71
config.address_v = GPUSampler::AddressMode::ClampToEdge;
72
config.address_w = GPUSampler::AddressMode::ClampToEdge;
73
config.min_filter = GPUSampler::Filter::Linear;
74
config.mag_filter = GPUSampler::Filter::Linear;
75
return config;
76
}
77
78
GPUShader::GPUShader(GPUShaderStage stage) : m_stage(stage)
79
{
80
}
81
82
GPUShader::~GPUShader() = default;
83
84
const char* GPUShader::GetStageName(GPUShaderStage stage)
85
{
86
static constexpr std::array<const char*, static_cast<u32>(GPUShaderStage::MaxCount)> names = {"Vertex", "Fragment",
87
"Geometry", "Compute"};
88
89
return names[static_cast<u32>(stage)];
90
}
91
92
GPUPipeline::GPUPipeline() = default;
93
94
GPUPipeline::~GPUPipeline() = default;
95
96
size_t GPUPipeline::InputLayoutHash::operator()(const InputLayout& il) const
97
{
98
std::size_t h = 0;
99
hash_combine(h, il.vertex_attributes.size(), il.vertex_stride);
100
101
for (const VertexAttribute& va : il.vertex_attributes)
102
hash_combine(h, va.key);
103
104
return h;
105
}
106
107
bool GPUPipeline::InputLayout::operator==(const InputLayout& rhs) const
108
{
109
return (vertex_stride == rhs.vertex_stride && vertex_attributes.size() == rhs.vertex_attributes.size() &&
110
std::memcmp(vertex_attributes.data(), rhs.vertex_attributes.data(),
111
sizeof(VertexAttribute) * rhs.vertex_attributes.size()) == 0);
112
}
113
114
bool GPUPipeline::InputLayout::operator!=(const InputLayout& rhs) const
115
{
116
return (vertex_stride != rhs.vertex_stride || vertex_attributes.size() != rhs.vertex_attributes.size() ||
117
std::memcmp(vertex_attributes.data(), rhs.vertex_attributes.data(),
118
sizeof(VertexAttribute) * rhs.vertex_attributes.size()) != 0);
119
}
120
121
GPUPipeline::RasterizationState GPUPipeline::RasterizationState::GetNoCullState()
122
{
123
RasterizationState ret = {};
124
ret.cull_mode = CullMode::None;
125
return ret;
126
}
127
128
GPUPipeline::DepthState GPUPipeline::DepthState::GetNoTestsState()
129
{
130
DepthState ret = {};
131
ret.depth_test = DepthFunc::Always;
132
return ret;
133
}
134
135
GPUPipeline::DepthState GPUPipeline::DepthState::GetAlwaysWriteState()
136
{
137
DepthState ret = {};
138
ret.depth_test = DepthFunc::Always;
139
ret.depth_write = true;
140
return ret;
141
}
142
143
GPUPipeline::BlendState GPUPipeline::BlendState::GetNoBlendingState()
144
{
145
BlendState ret = {};
146
ret.write_mask = 0xf;
147
return ret;
148
}
149
150
GPUPipeline::BlendState GPUPipeline::BlendState::GetAlphaBlendingState()
151
{
152
BlendState ret = {};
153
ret.enable = true;
154
ret.src_blend = BlendFunc::SrcAlpha;
155
ret.dst_blend = BlendFunc::InvSrcAlpha;
156
ret.blend_op = BlendOp::Add;
157
ret.src_alpha_blend = BlendFunc::One;
158
ret.dst_alpha_blend = BlendFunc::Zero;
159
ret.alpha_blend_op = BlendOp::Add;
160
ret.write_mask = 0xf;
161
return ret;
162
}
163
164
void GPUPipeline::GraphicsConfig::SetTargetFormats(GPUTexture::Format color_format,
165
GPUTexture::Format depth_format_ /* = GPUTexture::Format::Unknown */)
166
{
167
color_formats[0] = color_format;
168
for (size_t i = 1; i < std::size(color_formats); i++)
169
color_formats[i] = GPUTexture::Format::Unknown;
170
depth_format = depth_format_;
171
}
172
173
u32 GPUPipeline::GraphicsConfig::GetRenderTargetCount() const
174
{
175
u32 num_rts = 0;
176
for (; num_rts < static_cast<u32>(std::size(color_formats)); num_rts++)
177
{
178
if (color_formats[num_rts] == GPUTexture::Format::Unknown)
179
break;
180
}
181
return num_rts;
182
}
183
184
GPUTextureBuffer::GPUTextureBuffer(Format format, u32 size) : m_format(format), m_size_in_elements(size)
185
{
186
}
187
188
GPUTextureBuffer::~GPUTextureBuffer() = default;
189
190
u32 GPUTextureBuffer::GetElementSize(Format format)
191
{
192
static constexpr std::array<u32, static_cast<u32>(Format::MaxCount)> element_size = {{
193
sizeof(u16),
194
}};
195
196
return element_size[static_cast<u32>(format)];
197
}
198
199
bool GPUFramebufferManagerBase::Key::operator==(const Key& rhs) const
200
{
201
return (std::memcmp(this, &rhs, sizeof(*this)) == 0);
202
}
203
204
bool GPUFramebufferManagerBase::Key::operator!=(const Key& rhs) const
205
{
206
return (std::memcmp(this, &rhs, sizeof(*this)) != 0);
207
}
208
209
bool GPUFramebufferManagerBase::Key::ContainsRT(const GPUTexture* tex) const
210
{
211
// num_rts is worse for predictability.
212
for (u32 i = 0; i < GPUDevice::MAX_RENDER_TARGETS; i++)
213
{
214
if (rts[i] == tex)
215
return true;
216
}
217
return false;
218
}
219
220
size_t GPUFramebufferManagerBase::KeyHash::operator()(const Key& key) const
221
{
222
if constexpr (sizeof(void*) == 8)
223
return XXH3_64bits(&key, sizeof(key));
224
else
225
return XXH32(&key, sizeof(key), 0x1337);
226
}
227
228
GPUSwapChain::GPUSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, bool allow_present_throttle)
229
: m_window_info(wi), m_vsync_mode(vsync_mode), m_allow_present_throttle(allow_present_throttle)
230
{
231
}
232
233
GPUSwapChain::~GPUSwapChain() = default;
234
235
GSVector4i GPUSwapChain::PreRotateClipRect(WindowInfo::PreRotation prerotation, const GSVector2i surface_size,
236
const GSVector4i& v)
237
{
238
GSVector4i new_clip;
239
switch (prerotation)
240
{
241
case WindowInfo::PreRotation::Identity:
242
new_clip = v;
243
break;
244
245
case WindowInfo::PreRotation::Rotate90Clockwise:
246
{
247
const s32 height = (v.w - v.y);
248
const s32 y = surface_size.y - v.y - height;
249
new_clip = GSVector4i(y, v.x, y + height, v.z);
250
}
251
break;
252
253
case WindowInfo::PreRotation::Rotate180Clockwise:
254
{
255
const s32 width = (v.z - v.x);
256
const s32 height = (v.w - v.y);
257
const s32 x = surface_size.x - v.x - width;
258
const s32 y = surface_size.y - v.y - height;
259
new_clip = GSVector4i(x, y, x + width, y + height);
260
}
261
break;
262
263
case WindowInfo::PreRotation::Rotate270Clockwise:
264
{
265
const s32 width = (v.z - v.x);
266
const s32 x = surface_size.x - v.x - width;
267
new_clip = GSVector4i(v.y, x, v.w, x + width);
268
}
269
break;
270
271
DefaultCaseIsUnreachable()
272
}
273
274
return new_clip;
275
}
276
277
bool GPUSwapChain::IsExclusiveFullscreen() const
278
{
279
return false;
280
}
281
282
bool GPUSwapChain::ShouldSkipPresentingFrame()
283
{
284
// Only needed with FIFO. But since we're so fast, we allow it always.
285
if (!m_allow_present_throttle)
286
return false;
287
288
const float throttle_rate = (m_window_info.surface_refresh_rate > 0.0f) ? m_window_info.surface_refresh_rate : 60.0f;
289
const float throttle_period = 1.0f / throttle_rate;
290
291
const u64 now = Timer::GetCurrentValue();
292
const double diff = Timer::ConvertValueToSeconds(now - m_last_frame_displayed_time);
293
if (diff < throttle_period)
294
return true;
295
296
m_last_frame_displayed_time = now;
297
return false;
298
}
299
300
void GPUSwapChain::ThrottlePresentation()
301
{
302
const float throttle_rate = (m_window_info.surface_refresh_rate > 0.0f) ? m_window_info.surface_refresh_rate : 60.0f;
303
304
const u64 sleep_period = Timer::ConvertNanosecondsToValue(1e+9f / static_cast<double>(throttle_rate));
305
const u64 current_ts = Timer::GetCurrentValue();
306
307
// Allow it to fall behind/run ahead up to 2*period. Sleep isn't that precise, plus we need to
308
// allow time for the actual rendering.
309
const u64 max_variance = sleep_period * 2;
310
if (static_cast<u64>(std::abs(static_cast<s64>(current_ts - m_last_frame_displayed_time))) > max_variance)
311
m_last_frame_displayed_time = current_ts + sleep_period;
312
else
313
m_last_frame_displayed_time += sleep_period;
314
315
Timer::SleepUntil(m_last_frame_displayed_time, false);
316
}
317
318
GPUDevice::GPUDevice()
319
{
320
ResetStatistics();
321
}
322
323
GPUDevice::~GPUDevice() = default;
324
325
RenderAPI GPUDevice::GetPreferredAPI()
326
{
327
static RenderAPI preferred_renderer = RenderAPI::None;
328
if (preferred_renderer == RenderAPI::None) [[unlikely]]
329
{
330
#if defined(_WIN32) && !defined(_M_ARM64)
331
// Perfer DX11 on Windows, except ARM64, where QCom has slow DX11 drivers.
332
preferred_renderer = RenderAPI::D3D11;
333
#elif defined(_WIN32) && defined(_M_ARM64)
334
preferred_renderer = RenderAPI::D3D12;
335
#elif defined(__APPLE__)
336
// Prefer Metal on MacOS.
337
preferred_renderer = RenderAPI::Metal;
338
#elif defined(ENABLE_OPENGL) && defined(ENABLE_VULKAN)
339
// On Linux, if we have both GL and Vulkan, prefer VK if the driver isn't software.
340
preferred_renderer = VulkanDevice::IsSuitableDefaultRenderer() ? RenderAPI::Vulkan : RenderAPI::OpenGL;
341
#elif defined(ENABLE_OPENGL)
342
preferred_renderer = RenderAPI::OpenGL;
343
#elif defined(ENABLE_VULKAN)
344
preferred_renderer = RenderAPI::Vulkan;
345
#else
346
// Uhhh, what?
347
ERROR_LOG("Somehow don't have any renderers available...");
348
preferred_renderer = RenderAPI::None;
349
#endif
350
}
351
352
return preferred_renderer;
353
}
354
355
const char* GPUDevice::RenderAPIToString(RenderAPI api)
356
{
357
switch (api)
358
{
359
// clang-format off
360
#define CASE(x) case RenderAPI::x: return #x
361
CASE(None);
362
CASE(D3D11);
363
CASE(D3D12);
364
CASE(Metal);
365
CASE(Vulkan);
366
CASE(OpenGL);
367
CASE(OpenGLES);
368
#undef CASE
369
// clang-format on
370
default:
371
return "Unknown";
372
}
373
}
374
375
const char* GPUDevice::ShaderLanguageToString(GPUShaderLanguage language)
376
{
377
switch (language)
378
{
379
// clang-format off
380
#define CASE(x) case GPUShaderLanguage::x: return #x
381
CASE(HLSL);
382
CASE(GLSL);
383
CASE(GLSLES);
384
CASE(MSL);
385
CASE(SPV);
386
#undef CASE
387
// clang-format on
388
default:
389
return "Unknown";
390
}
391
}
392
393
const char* GPUDevice::VSyncModeToString(GPUVSyncMode mode)
394
{
395
static constexpr std::array<const char*, static_cast<size_t>(GPUVSyncMode::Count)> vsync_modes = {{
396
"Disabled",
397
"FIFO",
398
"Mailbox",
399
}};
400
401
return vsync_modes[static_cast<size_t>(mode)];
402
}
403
404
bool GPUDevice::IsSameRenderAPI(RenderAPI lhs, RenderAPI rhs)
405
{
406
return (lhs == rhs || ((lhs == RenderAPI::OpenGL || lhs == RenderAPI::OpenGLES) &&
407
(rhs == RenderAPI::OpenGL || rhs == RenderAPI::OpenGLES)));
408
}
409
410
GPUDevice::AdapterInfoList GPUDevice::GetAdapterListForAPI(RenderAPI api)
411
{
412
AdapterInfoList ret;
413
414
switch (api)
415
{
416
#ifdef ENABLE_VULKAN
417
case RenderAPI::Vulkan:
418
ret = VulkanDevice::GetAdapterList();
419
break;
420
#endif
421
422
#ifdef ENABLE_OPENGL
423
case RenderAPI::OpenGL:
424
case RenderAPI::OpenGLES:
425
// No way of querying.
426
break;
427
#endif
428
429
#ifdef _WIN32
430
case RenderAPI::D3D11:
431
case RenderAPI::D3D12:
432
ret = D3DCommon::GetAdapterInfoList();
433
break;
434
#endif
435
436
#ifdef __APPLE__
437
case RenderAPI::Metal:
438
ret = WrapGetMetalAdapterList();
439
break;
440
#endif
441
442
default:
443
break;
444
}
445
446
return ret;
447
}
448
449
bool GPUDevice::Create(std::string_view adapter, CreateFlags create_flags, std::string_view shader_dump_path,
450
std::string_view shader_cache_path, u32 shader_cache_version, const WindowInfo& wi,
451
GPUVSyncMode vsync, bool allow_present_throttle,
452
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
453
std::optional<bool> exclusive_fullscreen_control, Error* error)
454
{
455
m_debug_device = HasCreateFlag(create_flags, CreateFlags::EnableDebugDevice);
456
s_shader_dump_path = shader_dump_path;
457
458
INFO_LOG("Main render window is {}x{}.", wi.surface_width, wi.surface_height);
459
460
if (create_flags != CreateFlags::None) [[unlikely]]
461
{
462
WARNING_LOG("One or more non-standard creation flags are set:");
463
if (HasCreateFlag(create_flags, CreateFlags::EnableDebugDevice))
464
WARNING_LOG(" - Use debug device");
465
if (HasCreateFlag(create_flags, CreateFlags::EnableGPUValidation))
466
WARNING_LOG(" - Enable GPU validation");
467
if (HasCreateFlag(create_flags, CreateFlags::PreferGLESContext))
468
WARNING_LOG(" - Prefer OpenGL ES context");
469
if (HasCreateFlag(create_flags, CreateFlags::DisableDualSourceBlend))
470
WARNING_LOG(" - Disable dual source blend");
471
if (HasCreateFlag(create_flags, CreateFlags::DisableFeedbackLoops))
472
WARNING_LOG(" - Disable feedback loops");
473
if (HasCreateFlag(create_flags, CreateFlags::DisableFramebufferFetch))
474
WARNING_LOG(" - Disable framebuffer fetch");
475
if (HasCreateFlag(create_flags, CreateFlags::DisableTextureBuffers))
476
WARNING_LOG(" - Disable texture buffers");
477
if (HasCreateFlag(create_flags, CreateFlags::DisableGeometryShaders))
478
WARNING_LOG(" - Disable geometry shaders");
479
if (HasCreateFlag(create_flags, CreateFlags::DisableComputeShaders))
480
WARNING_LOG(" - Disable compute shaders");
481
if (HasCreateFlag(create_flags, CreateFlags::DisableTextureCopyToSelf))
482
WARNING_LOG(" - Disable texture copy to self");
483
if (HasCreateFlag(create_flags, CreateFlags::DisableMemoryImport))
484
WARNING_LOG(" - Disable memory import");
485
if (HasCreateFlag(create_flags, CreateFlags::DisableRasterOrderViews))
486
WARNING_LOG(" - Disable raster order views");
487
if (HasCreateFlag(create_flags, CreateFlags::DisableCompressedTextures))
488
WARNING_LOG(" - Disable compressed textures");
489
}
490
491
if (!CreateDeviceAndMainSwapChain(adapter, create_flags, wi, vsync, allow_present_throttle, exclusive_fullscreen_mode,
492
exclusive_fullscreen_control, error))
493
{
494
if (error && !error->IsValid())
495
error->SetStringView("Failed to create device.");
496
return false;
497
}
498
499
INFO_LOG("Render API: {} Version {}", RenderAPIToString(m_render_api), m_render_api_version);
500
INFO_LOG("Graphics Driver Info:\n{}", GetDriverInfo());
501
502
OpenShaderCache(shader_cache_path, shader_cache_version);
503
504
if (!CreateResources(error))
505
{
506
Error::AddPrefix(error, "Failed to create base resources.");
507
return false;
508
}
509
510
return true;
511
}
512
513
void GPUDevice::Destroy()
514
{
515
s_shader_dump_path = {};
516
517
PurgeTexturePool();
518
DestroyResources();
519
CloseShaderCache();
520
DestroyDevice();
521
}
522
523
bool GPUDevice::SwitchToSurfacelessRendering(Error* error)
524
{
525
// noop on everything except GL because of it's context nonsense
526
return true;
527
}
528
529
bool GPUDevice::RecreateMainSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, bool allow_present_throttle,
530
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
531
std::optional<bool> exclusive_fullscreen_control, Error* error)
532
{
533
534
m_main_swap_chain.reset();
535
m_main_swap_chain = CreateSwapChain(wi, vsync_mode, allow_present_throttle, exclusive_fullscreen_mode,
536
exclusive_fullscreen_control, error);
537
return static_cast<bool>(m_main_swap_chain);
538
}
539
540
void GPUDevice::DestroyMainSwapChain()
541
{
542
m_main_swap_chain.reset();
543
}
544
545
void GPUDevice::OpenShaderCache(std::string_view base_path, u32 version)
546
{
547
if (m_features.shader_cache && !base_path.empty())
548
{
549
const std::string basename = GetShaderCacheBaseName("shaders");
550
const std::string filename = Path::Combine(base_path, basename);
551
if (!m_shader_cache.Open(filename.c_str(), m_render_api_version, version))
552
{
553
WARNING_LOG("Failed to open shader cache. Creating new cache.");
554
if (!m_shader_cache.Create())
555
ERROR_LOG("Failed to create new shader cache.");
556
557
// Squish the pipeline cache too, it's going to be stale.
558
if (m_features.pipeline_cache)
559
{
560
const std::string pc_filename =
561
Path::Combine(base_path, TinyString::from_format("{}.bin", GetShaderCacheBaseName("pipelines")));
562
if (FileSystem::FileExists(pc_filename.c_str()))
563
{
564
INFO_LOG("Removing old pipeline cache '{}'", Path::GetFileName(pc_filename));
565
FileSystem::DeleteFile(pc_filename.c_str());
566
}
567
}
568
}
569
}
570
else
571
{
572
// Still need to set the version - GL needs it.
573
m_shader_cache.Open(std::string_view(), m_render_api_version, version);
574
}
575
576
s_pipeline_cache_path = {};
577
s_pipeline_cache_size = 0;
578
s_pipeline_cache_hash = {};
579
580
if (m_features.pipeline_cache && !base_path.empty())
581
{
582
Error error;
583
s_pipeline_cache_path =
584
Path::Combine(base_path, TinyString::from_format("{}.bin", GetShaderCacheBaseName("pipelines")));
585
if (FileSystem::FileExists(s_pipeline_cache_path.c_str()))
586
{
587
if (OpenPipelineCache(s_pipeline_cache_path, &error))
588
return;
589
590
WARNING_LOG("Failed to read pipeline cache '{}': {}", Path::GetFileName(s_pipeline_cache_path),
591
error.GetDescription());
592
}
593
594
if (!CreatePipelineCache(s_pipeline_cache_path, &error))
595
{
596
WARNING_LOG("Failed to create pipeline cache '{}': {}", Path::GetFileName(s_pipeline_cache_path),
597
error.GetDescription());
598
s_pipeline_cache_path = {};
599
}
600
}
601
}
602
603
void GPUDevice::CloseShaderCache()
604
{
605
m_shader_cache.Close();
606
607
if (!s_pipeline_cache_path.empty())
608
{
609
Error error;
610
if (!ClosePipelineCache(s_pipeline_cache_path, &error))
611
{
612
WARNING_LOG("Failed to close pipeline cache '{}': {}", Path::GetFileName(s_pipeline_cache_path),
613
error.GetDescription());
614
}
615
616
s_pipeline_cache_path = {};
617
}
618
}
619
620
std::string GPUDevice::GetShaderCacheBaseName(std::string_view type) const
621
{
622
const std::string_view debug_suffix = m_debug_device ? "_debug" : "";
623
624
TinyString lower_api_name(RenderAPIToString(m_render_api));
625
lower_api_name.convert_to_lower_case();
626
627
return fmt::format("{}_{}{}", lower_api_name, type, debug_suffix);
628
}
629
630
bool GPUDevice::OpenPipelineCache(const std::string& path, Error* error)
631
{
632
CompressHelpers::OptionalByteBuffer data =
633
CompressHelpers::DecompressFile(CompressHelpers::CompressType::Zstandard, path.c_str(), std::nullopt, error);
634
if (!data.has_value())
635
return false;
636
637
const size_t cache_size = data->size();
638
const std::array<u8, SHA1Digest::DIGEST_SIZE> cache_hash = SHA1Digest::GetDigest(data->cspan());
639
640
INFO_LOG("Loading {} byte pipeline cache with hash {}", cache_size, SHA1Digest::DigestToString(cache_hash));
641
if (!ReadPipelineCache(std::move(data.value()), error))
642
return false;
643
644
s_pipeline_cache_size = cache_size;
645
s_pipeline_cache_hash = cache_hash;
646
return true;
647
}
648
649
bool GPUDevice::CreatePipelineCache(const std::string& path, Error* error)
650
{
651
return false;
652
}
653
654
bool GPUDevice::ClosePipelineCache(const std::string& path, Error* error)
655
{
656
DynamicHeapArray<u8> data;
657
if (!GetPipelineCacheData(&data, error))
658
return false;
659
660
// Save disk writes if it hasn't changed, think of the poor SSDs.
661
if (s_pipeline_cache_size == data.size() && s_pipeline_cache_hash == SHA1Digest::GetDigest(data.cspan()))
662
{
663
INFO_LOG("Skipping updating pipeline cache '{}' due to no changes.", Path::GetFileName(path));
664
return true;
665
}
666
667
INFO_LOG("Compressing and writing {} bytes to '{}'", data.size(), Path::GetFileName(path));
668
return CompressHelpers::CompressToFile(CompressHelpers::CompressType::Zstandard, path.c_str(), data.cspan(), -1, true,
669
error);
670
}
671
672
bool GPUDevice::ReadPipelineCache(DynamicHeapArray<u8> data, Error* error)
673
{
674
return false;
675
}
676
677
bool GPUDevice::GetPipelineCacheData(DynamicHeapArray<u8>* data, Error* error)
678
{
679
return false;
680
}
681
682
bool GPUDevice::CreateResources(Error* error)
683
{
684
// Backend may initialize null texture itself if it needs it.
685
if (!m_empty_texture &&
686
!(m_empty_texture = CreateTexture(1, 1, 1, 1, 1, GPUTexture::Type::Texture, GPUTexture::Format::RGBA8,
687
GPUTexture::Flags::None, nullptr, 0, error)))
688
{
689
Error::AddPrefix(error, "Failed to create null texture: ");
690
return false;
691
}
692
GL_OBJECT_NAME(m_empty_texture, "Null Texture");
693
694
if (!(m_nearest_sampler = GetSampler(GPUSampler::GetNearestConfig(), error)) ||
695
!(m_linear_sampler = GetSampler(GPUSampler::GetLinearConfig(), error)))
696
{
697
Error::AddPrefix(error, "Failed to create samplers: ");
698
return false;
699
}
700
GL_OBJECT_NAME(m_nearest_sampler, "Nearest Sampler");
701
GL_OBJECT_NAME(m_linear_sampler, "Nearest Sampler");
702
return true;
703
}
704
705
void GPUDevice::DestroyResources()
706
{
707
m_empty_texture.reset();
708
709
m_linear_sampler = nullptr;
710
m_nearest_sampler = nullptr;
711
m_sampler_map.clear();
712
713
m_shader_cache.Close();
714
}
715
716
void GPUDevice::UploadVertexBuffer(const void* vertices, u32 vertex_size, u32 vertex_count, u32* base_vertex)
717
{
718
void* map;
719
u32 space;
720
MapVertexBuffer(vertex_size, vertex_count, &map, &space, base_vertex);
721
std::memcpy(map, vertices, vertex_size * vertex_count);
722
UnmapVertexBuffer(vertex_size, vertex_count);
723
}
724
725
void GPUDevice::UploadIndexBuffer(const u16* indices, u32 index_count, u32* base_index)
726
{
727
u16* map;
728
u32 space;
729
MapIndexBuffer(index_count, &map, &space, base_index);
730
std::memcpy(map, indices, sizeof(u16) * index_count);
731
UnmapIndexBuffer(index_count);
732
}
733
734
void GPUDevice::UploadUniformBuffer(const void* data, u32 data_size)
735
{
736
void* map = MapUniformBuffer(data_size);
737
std::memcpy(map, data, data_size);
738
UnmapUniformBuffer(data_size);
739
}
740
741
void GPUDevice::SetRenderTarget(GPUTexture* rt, GPUTexture* ds, GPUPipeline::RenderPassFlag render_pass_flags)
742
{
743
SetRenderTargets(rt ? &rt : nullptr, rt ? 1 : 0, ds, render_pass_flags);
744
}
745
746
void GPUDevice::SetViewport(s32 x, s32 y, s32 width, s32 height)
747
{
748
SetViewport(GSVector4i(x, y, x + width, y + height));
749
}
750
751
void GPUDevice::SetScissor(s32 x, s32 y, s32 width, s32 height)
752
{
753
SetScissor(GSVector4i(x, y, x + width, y + height));
754
}
755
756
void GPUDevice::SetViewportAndScissor(s32 x, s32 y, s32 width, s32 height)
757
{
758
SetViewportAndScissor(GSVector4i(x, y, x + width, y + height));
759
}
760
761
void GPUDevice::SetViewportAndScissor(const GSVector4i rc)
762
{
763
SetViewport(rc);
764
SetScissor(rc);
765
}
766
767
void GPUDevice::ClearRenderTarget(GPUTexture* t, u32 c)
768
{
769
t->SetClearColor(c);
770
}
771
772
void GPUDevice::ClearDepth(GPUTexture* t, float d)
773
{
774
t->SetClearDepth(d);
775
}
776
777
void GPUDevice::InvalidateRenderTarget(GPUTexture* t)
778
{
779
t->SetState(GPUTexture::State::Invalidated);
780
}
781
782
std::unique_ptr<GPUShader> GPUDevice::CreateShader(GPUShaderStage stage, GPUShaderLanguage language,
783
std::string_view source, Error* error /* = nullptr */,
784
const char* entry_point /* = "main" */)
785
{
786
std::unique_ptr<GPUShader> shader;
787
if (!m_shader_cache.IsOpen())
788
{
789
shader = CreateShaderFromSource(stage, language, source, entry_point, nullptr, error);
790
return shader;
791
}
792
793
const GPUShaderCache::CacheIndexKey key = m_shader_cache.GetCacheKey(stage, language, source, entry_point);
794
std::optional<GPUShaderCache::ShaderBinary> binary = m_shader_cache.Lookup(key);
795
if (binary.has_value())
796
{
797
shader = CreateShaderFromBinary(stage, binary->cspan(), error);
798
if (shader)
799
return shader;
800
801
ERROR_LOG("Failed to create shader from binary (driver changed?). Clearing cache.");
802
m_shader_cache.Clear();
803
binary.reset();
804
}
805
806
GPUShaderCache::ShaderBinary new_binary;
807
shader = CreateShaderFromSource(stage, language, source, entry_point, &new_binary, error);
808
if (!shader)
809
return shader;
810
811
// Don't insert empty shaders into the cache...
812
if (!new_binary.empty())
813
{
814
if (!m_shader_cache.Insert(key, new_binary.data(), static_cast<u32>(new_binary.size())))
815
m_shader_cache.Close();
816
}
817
818
return shader;
819
}
820
821
std::optional<GPUDevice::ExclusiveFullscreenMode> GPUDevice::ExclusiveFullscreenMode::Parse(std::string_view str)
822
{
823
std::optional<ExclusiveFullscreenMode> ret;
824
std::string_view::size_type sep1 = str.find('x');
825
if (sep1 != std::string_view::npos)
826
{
827
std::optional<u32> owidth = StringUtil::FromChars<u32>(str.substr(0, sep1));
828
sep1++;
829
830
while (sep1 < str.length() && StringUtil::IsWhitespace(str[sep1]))
831
sep1++;
832
833
if (owidth.has_value() && sep1 < str.length())
834
{
835
std::string_view::size_type sep2 = str.find('@', sep1);
836
if (sep2 != std::string_view::npos)
837
{
838
std::optional<u32> oheight = StringUtil::FromChars<u32>(str.substr(sep1, sep2 - sep1));
839
sep2++;
840
841
while (sep2 < str.length() && StringUtil::IsWhitespace(str[sep2]))
842
sep2++;
843
844
if (oheight.has_value() && sep2 < str.length())
845
{
846
std::optional<float> orefresh_rate = StringUtil::FromChars<float>(str.substr(sep2));
847
if (orefresh_rate.has_value())
848
{
849
ret = ExclusiveFullscreenMode{
850
.width = owidth.value(), .height = oheight.value(), .refresh_rate = orefresh_rate.value()};
851
}
852
}
853
}
854
}
855
}
856
857
return ret;
858
}
859
860
TinyString GPUDevice::ExclusiveFullscreenMode::ToString() const
861
{
862
return TinyString::from_format("{} x {} @ {} hz", width, height, refresh_rate);
863
}
864
865
void GPUDevice::DumpBadShader(std::string_view code, std::string_view errors)
866
{
867
static u32 next_bad_shader_id = 0;
868
869
if (s_shader_dump_path.empty())
870
return;
871
872
const std::string filename =
873
Path::Combine(s_shader_dump_path, TinyString::from_format("bad_shader_{}.txt", ++next_bad_shader_id));
874
auto fp = FileSystem::OpenManagedCFile(filename.c_str(), "wb");
875
if (fp)
876
{
877
if (!code.empty())
878
std::fwrite(code.data(), code.size(), 1, fp.get());
879
std::fputs("\n\n**** ERRORS ****\n", fp.get());
880
if (!errors.empty())
881
std::fwrite(errors.data(), errors.size(), 1, fp.get());
882
}
883
}
884
885
std::array<float, 4> GPUDevice::RGBA8ToFloat(u32 rgba)
886
{
887
return std::array<float, 4>{static_cast<float>(rgba & UINT32_C(0xFF)) * (1.0f / 255.0f),
888
static_cast<float>((rgba >> 8) & UINT32_C(0xFF)) * (1.0f / 255.0f),
889
static_cast<float>((rgba >> 16) & UINT32_C(0xFF)) * (1.0f / 255.0f),
890
static_cast<float>(rgba >> 24) * (1.0f / 255.0f)};
891
}
892
893
bool GPUDevice::UsesLowerLeftOrigin() const
894
{
895
const RenderAPI api = GetRenderAPI();
896
return (api == RenderAPI::OpenGL || api == RenderAPI::OpenGLES);
897
}
898
899
GSVector4i GPUDevice::FlipToLowerLeft(GSVector4i rc, s32 target_height)
900
{
901
const s32 height = rc.height();
902
const s32 flipped_y = target_height - rc.top - height;
903
rc.top = flipped_y;
904
rc.bottom = flipped_y + height;
905
return rc;
906
}
907
908
GPUSampler* GPUDevice::GetSampler(const GPUSampler::Config& config, Error* error /* = nullptr */)
909
{
910
auto it = m_sampler_map.find(config.key);
911
if (it != m_sampler_map.end())
912
{
913
if (!it->second) [[unlikely]]
914
Error::SetStringView(error, "Sampler previously failed creation.");
915
916
return it->second.get();
917
}
918
919
std::unique_ptr<GPUSampler> sampler = g_gpu_device->CreateSampler(config, error);
920
if (sampler)
921
GL_OBJECT_NAME_FMT(sampler, "Sampler {:016X}", config.key);
922
923
it = m_sampler_map.emplace(config.key, std::move(sampler)).first;
924
return it->second.get();
925
}
926
927
bool GPUDevice::IsTexturePoolType(GPUTexture::Type type)
928
{
929
return (type == GPUTexture::Type::Texture);
930
}
931
932
std::unique_ptr<GPUTexture> GPUDevice::FetchTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples,
933
GPUTexture::Type type, GPUTexture::Format format,
934
GPUTexture::Flags flags, const void* data /* = nullptr */,
935
u32 data_stride /* = 0 */, Error* error /* = nullptr */)
936
{
937
std::unique_ptr<GPUTexture> ret;
938
939
const TexturePoolKey key = {static_cast<u16>(width),
940
static_cast<u16>(height),
941
static_cast<u8>(layers),
942
static_cast<u8>(levels),
943
static_cast<u8>(samples),
944
type,
945
format,
946
flags};
947
948
const bool is_texture = IsTexturePoolType(type);
949
TexturePool& pool = is_texture ? m_texture_pool : m_target_pool;
950
const u32 pool_size = (is_texture ? MAX_TEXTURE_POOL_SIZE : MAX_TARGET_POOL_SIZE);
951
952
TexturePool::iterator it;
953
954
if (is_texture && data && m_features.prefer_unused_textures)
955
{
956
// Try to find a texture that wasn't used this frame first.
957
for (it = m_texture_pool.begin(); it != m_texture_pool.end(); ++it)
958
{
959
if (it->use_counter == m_texture_pool_counter)
960
{
961
// We're into textures recycled this frame, not going to find anything newer.
962
// But prefer reuse over creating a new texture.
963
if (m_texture_pool.size() < pool_size)
964
{
965
it = m_texture_pool.end();
966
break;
967
}
968
}
969
970
if (it->key == key)
971
break;
972
}
973
}
974
else
975
{
976
for (it = pool.begin(); it != pool.end(); ++it)
977
{
978
if (it->key == key)
979
break;
980
}
981
}
982
983
if (it != pool.end())
984
{
985
if (!data || it->texture->Update(0, 0, width, height, data, data_stride, 0, 0))
986
{
987
ret = std::move(it->texture);
988
pool.erase(it);
989
return ret;
990
}
991
else
992
{
993
// This shouldn't happen...
994
ERROR_LOG("Failed to upload {}x{} to pooled texture", width, height);
995
}
996
}
997
998
Error create_error;
999
ret = CreateTexture(width, height, layers, levels, samples, type, format, flags, data, data_stride, &create_error);
1000
if (!ret) [[unlikely]]
1001
{
1002
Error::SetStringFmt(
1003
error ? error : &create_error, "Failed to create {}x{} {} {}: {}", width, height,
1004
GPUTexture::GetFormatName(format),
1005
((type == GPUTexture::Type::RenderTarget) ? "RT" : (type == GPUTexture::Type::DepthStencil ? "DS" : "Texture")),
1006
create_error.GetDescription());
1007
if (!error)
1008
ERROR_LOG(create_error.GetDescription());
1009
}
1010
1011
return ret;
1012
}
1013
1014
GPUDevice::AutoRecycleTexture
1015
GPUDevice::FetchAutoRecycleTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples, GPUTexture::Type type,
1016
GPUTexture::Format format, GPUTexture::Flags flags, const void* data /* = nullptr */,
1017
u32 data_stride /* = 0 */, Error* error /* = nullptr */)
1018
{
1019
std::unique_ptr<GPUTexture> ret =
1020
FetchTexture(width, height, layers, levels, samples, type, format, flags, data, data_stride, error);
1021
return std::unique_ptr<GPUTexture, PooledTextureDeleter>(ret.release());
1022
}
1023
1024
std::unique_ptr<GPUTexture> GPUDevice::FetchAndUploadTextureImage(const Image& image,
1025
GPUTexture::Flags flags /*= GPUTexture::Flags::None*/,
1026
Error* error /*= nullptr*/)
1027
{
1028
const Image* image_to_upload = &image;
1029
GPUTexture::Format gpu_format = GPUTexture::GetTextureFormatForImageFormat(image.GetFormat());
1030
bool gpu_format_supported;
1031
1032
// avoid device query for compressed formats that we've already pretested
1033
if (gpu_format >= GPUTexture::Format::BC1 && gpu_format <= GPUTexture::Format::BC3)
1034
gpu_format_supported = m_features.dxt_textures;
1035
else if (gpu_format == GPUTexture::Format::BC7)
1036
gpu_format_supported = m_features.bptc_textures;
1037
else if (gpu_format == GPUTexture::Format::RGBA8) // always supported
1038
gpu_format_supported = true;
1039
else if (gpu_format != GPUTexture::Format::Unknown)
1040
gpu_format_supported = SupportsTextureFormat(gpu_format);
1041
else
1042
gpu_format_supported = false;
1043
1044
std::optional<Image> converted_image;
1045
if (!gpu_format_supported)
1046
{
1047
converted_image = image.ConvertToRGBA8(error);
1048
if (!converted_image.has_value())
1049
return nullptr;
1050
1051
image_to_upload = &converted_image.value();
1052
gpu_format = GPUTexture::GetTextureFormatForImageFormat(converted_image->GetFormat());
1053
}
1054
1055
return FetchTexture(image_to_upload->GetWidth(), image_to_upload->GetHeight(), 1, 1, 1, GPUTexture::Type::Texture,
1056
gpu_format, flags, image_to_upload->GetPixels(), image_to_upload->GetPitch(), error);
1057
}
1058
1059
void GPUDevice::RecycleTexture(std::unique_ptr<GPUTexture> texture)
1060
{
1061
if (!texture)
1062
return;
1063
1064
const TexturePoolKey key = {static_cast<u16>(texture->GetWidth()),
1065
static_cast<u16>(texture->GetHeight()),
1066
static_cast<u8>(texture->GetLayers()),
1067
static_cast<u8>(texture->GetLevels()),
1068
static_cast<u8>(texture->GetSamples()),
1069
texture->GetType(),
1070
texture->GetFormat(),
1071
texture->GetFlags()};
1072
1073
const bool is_texture = IsTexturePoolType(texture->GetType());
1074
TexturePool& pool = is_texture ? m_texture_pool : m_target_pool;
1075
pool.push_back({std::move(texture), m_texture_pool_counter, key});
1076
1077
const u32 max_size = is_texture ? MAX_TEXTURE_POOL_SIZE : MAX_TARGET_POOL_SIZE;
1078
while (pool.size() > max_size)
1079
{
1080
DEBUG_LOG("Trim {}x{} texture from pool", pool.front().texture->GetWidth(), pool.front().texture->GetHeight());
1081
pool.pop_front();
1082
}
1083
}
1084
1085
void GPUDevice::PurgeTexturePool()
1086
{
1087
m_texture_pool_counter = 0;
1088
m_texture_pool.clear();
1089
m_target_pool.clear();
1090
}
1091
1092
void GPUDevice::TrimTexturePool()
1093
{
1094
GL_INS_FMT("Texture Pool Size: {}", m_texture_pool.size());
1095
GL_INS_FMT("Target Pool Size: {}", m_target_pool.size());
1096
GL_INS_FMT("VRAM Usage: {:.2f} MB", s_total_vram_usage / 1048576.0);
1097
1098
DEBUG_LOG("Texture Pool Size: {} Target Pool Size: {} VRAM: {:.2f} MB", m_texture_pool.size(), m_target_pool.size(),
1099
s_total_vram_usage / 1048756.0);
1100
1101
if (m_texture_pool.empty() && m_target_pool.empty())
1102
return;
1103
1104
const u32 prev_counter = m_texture_pool_counter++;
1105
for (u32 pool_idx = 0; pool_idx < 2; pool_idx++)
1106
{
1107
TexturePool& pool = pool_idx ? m_target_pool : m_texture_pool;
1108
for (auto it = pool.begin(); it != pool.end();)
1109
{
1110
const u32 delta = (prev_counter - it->use_counter);
1111
if (delta < POOL_PURGE_DELAY)
1112
break;
1113
1114
DEBUG_LOG("Trim {}x{} texture from pool", it->texture->GetWidth(), it->texture->GetHeight());
1115
it = pool.erase(it);
1116
}
1117
}
1118
1119
if (m_texture_pool_counter < prev_counter) [[unlikely]]
1120
{
1121
// wrapped around, handle it
1122
if (m_texture_pool.empty() && m_target_pool.empty())
1123
{
1124
m_texture_pool_counter = 0;
1125
}
1126
else
1127
{
1128
const u32 texture_min =
1129
m_texture_pool.empty() ? std::numeric_limits<u32>::max() : m_texture_pool.front().use_counter;
1130
const u32 target_min =
1131
m_target_pool.empty() ? std::numeric_limits<u32>::max() : m_target_pool.front().use_counter;
1132
const u32 reduce = std::min(texture_min, target_min);
1133
m_texture_pool_counter -= reduce;
1134
for (u32 pool_idx = 0; pool_idx < 2; pool_idx++)
1135
{
1136
TexturePool& pool = pool_idx ? m_target_pool : m_texture_pool;
1137
for (TexturePoolEntry& entry : pool)
1138
entry.use_counter -= reduce;
1139
}
1140
}
1141
}
1142
}
1143
1144
bool GPUDevice::ResizeTexture(std::unique_ptr<GPUTexture>* tex, u32 new_width, u32 new_height, GPUTexture::Type type,
1145
GPUTexture::Format format, GPUTexture::Flags flags, bool preserve /* = true */,
1146
Error* error /* = nullptr */)
1147
{
1148
GPUTexture* old_tex = tex->get();
1149
if (old_tex && old_tex->GetWidth() == new_width && old_tex->GetHeight() == new_height && old_tex->GetType() == type &&
1150
old_tex->GetFormat() == format && old_tex->GetFlags() == flags)
1151
{
1152
return true;
1153
}
1154
1155
DebugAssert(!old_tex || (old_tex->GetLayers() == 1 && old_tex->GetLevels() == 1 && old_tex->GetSamples() == 1));
1156
std::unique_ptr<GPUTexture> new_tex =
1157
FetchTexture(new_width, new_height, 1, 1, 1, type, format, flags, nullptr, 0, error);
1158
if (!new_tex) [[unlikely]]
1159
return false;
1160
1161
if (preserve)
1162
{
1163
if (old_tex)
1164
{
1165
if (old_tex->GetState() == GPUTexture::State::Cleared)
1166
{
1167
if (type == GPUTexture::Type::RenderTarget)
1168
ClearRenderTarget(new_tex.get(), old_tex->GetClearColor());
1169
}
1170
else if (old_tex->GetState() == GPUTexture::State::Dirty)
1171
{
1172
const u32 copy_width = std::min(new_width, old_tex->GetWidth());
1173
const u32 copy_height = std::min(new_height, old_tex->GetHeight());
1174
if (type == GPUTexture::Type::RenderTarget)
1175
ClearRenderTarget(new_tex.get(), 0);
1176
1177
if (old_tex->GetFormat() == new_tex->GetFormat())
1178
CopyTextureRegion(new_tex.get(), 0, 0, 0, 0, old_tex, 0, 0, 0, 0, copy_width, copy_height);
1179
}
1180
}
1181
else
1182
{
1183
// If we're expecting data to be there, make sure to clear it.
1184
if (type == GPUTexture::Type::RenderTarget)
1185
ClearRenderTarget(new_tex.get(), 0);
1186
}
1187
}
1188
1189
RecycleTexture(std::move(*tex));
1190
*tex = std::move(new_tex);
1191
return true;
1192
}
1193
1194
bool GPUDevice::ResizeTexture(std::unique_ptr<GPUTexture>* tex, u32 new_width, u32 new_height, GPUTexture::Type type,
1195
GPUTexture::Format format, GPUTexture::Flags flags, const void* replace_data,
1196
u32 replace_data_pitch, Error* error /* = nullptr */)
1197
{
1198
GPUTexture* old_tex = tex->get();
1199
if (old_tex && old_tex->GetWidth() == new_width && old_tex->GetHeight() == new_height && old_tex->GetType() == type &&
1200
old_tex->GetFormat() == format && old_tex->GetFlags() == flags)
1201
{
1202
if (replace_data && !old_tex->Update(0, 0, new_width, new_height, replace_data, replace_data_pitch))
1203
{
1204
Error::SetStringView(error, "Texture update failed.");
1205
return false;
1206
}
1207
1208
return true;
1209
}
1210
1211
DebugAssert(!old_tex || (old_tex->GetLayers() == 1 && old_tex->GetLevels() == 1 && old_tex->GetSamples() == 1));
1212
std::unique_ptr<GPUTexture> new_tex =
1213
FetchTexture(new_width, new_height, 1, 1, 1, type, format, flags, replace_data, replace_data_pitch, error);
1214
if (!new_tex) [[unlikely]]
1215
return false;
1216
1217
RecycleTexture(std::move(*tex));
1218
*tex = std::move(new_tex);
1219
return true;
1220
}
1221
1222
bool GPUDevice::SetGPUTimingEnabled(bool enabled)
1223
{
1224
return false;
1225
}
1226
1227
float GPUDevice::GetAndResetAccumulatedGPUTime()
1228
{
1229
return 0.0f;
1230
}
1231
1232
void GPUDevice::ResetStatistics()
1233
{
1234
s_stats = {};
1235
}
1236
1237
GPUDriverType GPUDevice::GuessDriverType(u32 pci_vendor_id, std::string_view vendor_name, std::string_view adapter_name)
1238
{
1239
#define ACHECK(name) (adapter_name.find(name) != std::string_view::npos)
1240
#define VCHECK(name) (vendor_name.find(name) != std::string_view::npos)
1241
#define MESA_CHECK (ACHECK("Mesa") || VCHECK("Mesa"))
1242
1243
if (pci_vendor_id == 0x1002 || pci_vendor_id == 0x1022 || VCHECK("Advanced Micro Devices") ||
1244
VCHECK("ATI Technologies Inc.") || VCHECK("ATI"))
1245
{
1246
INFO_LOG("AMD GPU detected.");
1247
return MESA_CHECK ? GPUDriverType::AMDMesa : GPUDriverType::AMDProprietary;
1248
}
1249
else if (pci_vendor_id == 0x10DE || VCHECK("NVIDIA Corporation"))
1250
{
1251
INFO_LOG("NVIDIA GPU detected.");
1252
return MESA_CHECK ? GPUDriverType::NVIDIAMesa : GPUDriverType::NVIDIAProprietary;
1253
}
1254
else if (pci_vendor_id == 0x8086 || VCHECK("Intel"))
1255
{
1256
INFO_LOG("Intel GPU detected.");
1257
return MESA_CHECK ? GPUDriverType::IntelMesa : GPUDriverType::IntelProprietary;
1258
}
1259
else if (pci_vendor_id == 0x5143 || VCHECK("Qualcomm") || ACHECK("Adreno"))
1260
{
1261
INFO_LOG("Qualcomm GPU detected.");
1262
return MESA_CHECK ? GPUDriverType::QualcommMesa : GPUDriverType::QualcommProprietary;
1263
}
1264
else if (pci_vendor_id == 0x13B5 || VCHECK("ARM") || ACHECK("Mali"))
1265
{
1266
INFO_LOG("ARM GPU detected.");
1267
return MESA_CHECK ? GPUDriverType::ARMMesa : GPUDriverType::ARMProprietary;
1268
}
1269
else if (pci_vendor_id == 0x1010 || VCHECK("Imagination Technologies") || ACHECK("PowerVR"))
1270
{
1271
INFO_LOG("Imagination GPU detected.");
1272
return MESA_CHECK ? GPUDriverType::ImaginationMesa : GPUDriverType::ImaginationProprietary;
1273
}
1274
else if (pci_vendor_id == 0x14E4 || VCHECK("Broadcom") || ACHECK("VideoCore"))
1275
{
1276
INFO_LOG("Broadcom GPU detected.");
1277
return MESA_CHECK ? GPUDriverType::BroadcomMesa : GPUDriverType::BroadcomProprietary;
1278
}
1279
else
1280
{
1281
WARNING_LOG("Unknown GPU vendor with PCI ID 0x{:04X}, adapter='{}', vendor='{}'", pci_vendor_id, adapter_name,
1282
vendor_name);
1283
return GPUDriverType::Unknown;
1284
}
1285
1286
#undef MESA_CHECK
1287
#undef VCHECK
1288
#undef ACHECK
1289
}
1290
1291
void GPUDevice::SetDriverType(GPUDriverType type)
1292
{
1293
m_driver_type = type;
1294
1295
#define NTENTRY(n) {GPUDriverType::n, #n}
1296
static constexpr const std::pair<GPUDriverType, const char*> name_table[] = {
1297
NTENTRY(Unknown),
1298
NTENTRY(AMDProprietary),
1299
NTENTRY(AMDMesa),
1300
NTENTRY(IntelProprietary),
1301
NTENTRY(IntelMesa),
1302
NTENTRY(NVIDIAProprietary),
1303
NTENTRY(NVIDIAMesa),
1304
NTENTRY(AppleProprietary),
1305
NTENTRY(AppleMesa),
1306
NTENTRY(DozenMesa),
1307
1308
NTENTRY(ImaginationProprietary),
1309
NTENTRY(ImaginationMesa),
1310
NTENTRY(ARMProprietary),
1311
NTENTRY(ARMMesa),
1312
NTENTRY(QualcommProprietary),
1313
NTENTRY(QualcommMesa),
1314
NTENTRY(BroadcomProprietary),
1315
NTENTRY(BroadcomMesa),
1316
1317
NTENTRY(LLVMPipe),
1318
NTENTRY(SwiftShader),
1319
};
1320
#undef NTENTRY
1321
1322
const auto iter =
1323
std::find_if(std::begin(name_table), std::end(name_table), [&type](const auto& it) { return it.first == type; });
1324
INFO_LOG("Driver type set to {}.", (iter == std::end(name_table)) ? name_table[0].second : iter->second);
1325
}
1326
1327
std::unique_ptr<GPUDevice> GPUDevice::CreateDeviceForAPI(RenderAPI api)
1328
{
1329
switch (api)
1330
{
1331
#ifdef ENABLE_VULKAN
1332
case RenderAPI::Vulkan:
1333
return std::make_unique<VulkanDevice>();
1334
#endif
1335
1336
#ifdef ENABLE_OPENGL
1337
case RenderAPI::OpenGL:
1338
case RenderAPI::OpenGLES:
1339
return std::make_unique<OpenGLDevice>();
1340
#endif
1341
1342
#ifdef _WIN32
1343
case RenderAPI::D3D12:
1344
return std::make_unique<D3D12Device>();
1345
1346
case RenderAPI::D3D11:
1347
return std::make_unique<D3D11Device>();
1348
#endif
1349
1350
#ifdef __APPLE__
1351
case RenderAPI::Metal:
1352
return WrapNewMetalDevice();
1353
#endif
1354
1355
default:
1356
return {};
1357
}
1358
}
1359
1360
#ifndef _WIN32
1361
// Use a duckstation-suffixed shaderc name to avoid conflicts and loading another shaderc, e.g. from the Vulkan SDK.
1362
#define SHADERC_LIB_NAME "shaderc_ds"
1363
#else
1364
#define SHADERC_LIB_NAME "shaderc_shared"
1365
#endif
1366
1367
#define SHADERC_FUNCTIONS(X) \
1368
X(shaderc_compiler_initialize) \
1369
X(shaderc_compiler_release) \
1370
X(shaderc_compile_options_initialize) \
1371
X(shaderc_compile_options_release) \
1372
X(shaderc_compile_options_set_source_language) \
1373
X(shaderc_compile_options_set_generate_debug_info) \
1374
X(shaderc_compile_options_set_optimization_level) \
1375
X(shaderc_compile_options_set_target_env) \
1376
X(shaderc_compilation_status_to_string) \
1377
X(shaderc_compile_into_spv) \
1378
X(shaderc_result_release) \
1379
X(shaderc_result_get_length) \
1380
X(shaderc_result_get_num_warnings) \
1381
X(shaderc_result_get_bytes) \
1382
X(shaderc_result_get_compilation_status) \
1383
X(shaderc_result_get_error_message) \
1384
X(shaderc_optimize_spv)
1385
1386
#define SPIRV_CROSS_FUNCTIONS(X) \
1387
X(spvc_context_create) \
1388
X(spvc_context_destroy) \
1389
X(spvc_context_set_error_callback) \
1390
X(spvc_context_parse_spirv) \
1391
X(spvc_context_create_compiler) \
1392
X(spvc_compiler_create_compiler_options) \
1393
X(spvc_compiler_create_shader_resources) \
1394
X(spvc_compiler_get_execution_model) \
1395
X(spvc_compiler_options_set_bool) \
1396
X(spvc_compiler_options_set_uint) \
1397
X(spvc_compiler_install_compiler_options) \
1398
X(spvc_compiler_require_extension) \
1399
X(spvc_compiler_compile) \
1400
X(spvc_resources_get_resource_list_for_type)
1401
1402
#ifdef _WIN32
1403
#define SPIRV_CROSS_HLSL_FUNCTIONS(X) X(spvc_compiler_hlsl_add_resource_binding)
1404
#else
1405
#define SPIRV_CROSS_HLSL_FUNCTIONS(X)
1406
#endif
1407
#ifdef __APPLE__
1408
#define SPIRV_CROSS_MSL_FUNCTIONS(X) X(spvc_compiler_msl_add_resource_binding)
1409
#else
1410
#define SPIRV_CROSS_MSL_FUNCTIONS(X)
1411
#endif
1412
1413
// TODO: NOT thread safe, yet.
1414
namespace dyn_libs {
1415
static bool OpenShaderc(Error* error);
1416
static void CloseShaderc();
1417
static bool OpenSpirvCross(Error* error);
1418
static void CloseSpirvCross();
1419
static void CloseAll();
1420
1421
static DynamicLibrary s_shaderc_library;
1422
static DynamicLibrary s_spirv_cross_library;
1423
1424
static shaderc_compiler_t s_shaderc_compiler = nullptr;
1425
1426
static bool s_close_registered = false;
1427
1428
#define ADD_FUNC(F) static decltype(&::F) F;
1429
SHADERC_FUNCTIONS(ADD_FUNC)
1430
SPIRV_CROSS_FUNCTIONS(ADD_FUNC)
1431
SPIRV_CROSS_HLSL_FUNCTIONS(ADD_FUNC)
1432
SPIRV_CROSS_MSL_FUNCTIONS(ADD_FUNC)
1433
#undef ADD_FUNC
1434
1435
} // namespace dyn_libs
1436
1437
bool dyn_libs::OpenShaderc(Error* error)
1438
{
1439
if (s_shaderc_library.IsOpen())
1440
return true;
1441
1442
const std::string libname = DynamicLibrary::GetVersionedFilename(SHADERC_LIB_NAME);
1443
if (!s_shaderc_library.Open(libname.c_str(), error))
1444
{
1445
Error::AddPrefix(error, "Failed to load shaderc: ");
1446
return false;
1447
}
1448
1449
#define LOAD_FUNC(F) \
1450
if (!s_shaderc_library.GetSymbol(#F, &F)) \
1451
{ \
1452
Error::SetStringFmt(error, "Failed to find function {}", #F); \
1453
CloseShaderc(); \
1454
return false; \
1455
}
1456
1457
SHADERC_FUNCTIONS(LOAD_FUNC)
1458
#undef LOAD_FUNC
1459
1460
s_shaderc_compiler = shaderc_compiler_initialize();
1461
if (!s_shaderc_compiler)
1462
{
1463
Error::SetStringView(error, "shaderc_compiler_initialize() failed");
1464
CloseShaderc();
1465
return false;
1466
}
1467
1468
if (!s_close_registered)
1469
{
1470
s_close_registered = true;
1471
std::atexit(&dyn_libs::CloseAll);
1472
}
1473
1474
return true;
1475
}
1476
1477
void dyn_libs::CloseShaderc()
1478
{
1479
if (s_shaderc_compiler)
1480
{
1481
shaderc_compiler_release(s_shaderc_compiler);
1482
s_shaderc_compiler = nullptr;
1483
}
1484
1485
#define UNLOAD_FUNC(F) F = nullptr;
1486
SHADERC_FUNCTIONS(UNLOAD_FUNC)
1487
#undef UNLOAD_FUNC
1488
1489
s_shaderc_library.Close();
1490
}
1491
1492
bool dyn_libs::OpenSpirvCross(Error* error)
1493
{
1494
if (s_spirv_cross_library.IsOpen())
1495
return true;
1496
1497
#if defined(_WIN32) || defined(__ANDROID__)
1498
// SPVC's build on Windows doesn't spit out a versioned DLL.
1499
const std::string libname = DynamicLibrary::GetVersionedFilename("spirv-cross-c-shared");
1500
#else
1501
const std::string libname = DynamicLibrary::GetVersionedFilename("spirv-cross-c-shared", SPVC_C_API_VERSION_MAJOR);
1502
#endif
1503
if (!s_spirv_cross_library.Open(libname.c_str(), error))
1504
{
1505
Error::AddPrefix(error, "Failed to load spirv-cross: ");
1506
return false;
1507
}
1508
1509
#define LOAD_FUNC(F) \
1510
if (!s_spirv_cross_library.GetSymbol(#F, &F)) \
1511
{ \
1512
Error::SetStringFmt(error, "Failed to find function {}", #F); \
1513
CloseSpirvCross(); \
1514
return false; \
1515
}
1516
1517
SPIRV_CROSS_FUNCTIONS(LOAD_FUNC)
1518
SPIRV_CROSS_HLSL_FUNCTIONS(LOAD_FUNC)
1519
SPIRV_CROSS_MSL_FUNCTIONS(LOAD_FUNC)
1520
#undef LOAD_FUNC
1521
1522
if (!s_close_registered)
1523
{
1524
s_close_registered = true;
1525
std::atexit(&dyn_libs::CloseAll);
1526
}
1527
1528
return true;
1529
}
1530
1531
void dyn_libs::CloseSpirvCross()
1532
{
1533
#define UNLOAD_FUNC(F) F = nullptr;
1534
SPIRV_CROSS_FUNCTIONS(UNLOAD_FUNC)
1535
SPIRV_CROSS_HLSL_FUNCTIONS(UNLOAD_FUNC)
1536
SPIRV_CROSS_MSL_FUNCTIONS(UNLOAD_FUNC)
1537
#undef UNLOAD_FUNC
1538
1539
s_spirv_cross_library.Close();
1540
}
1541
1542
void dyn_libs::CloseAll()
1543
{
1544
CloseShaderc();
1545
CloseSpirvCross();
1546
}
1547
1548
#undef SPIRV_CROSS_HLSL_FUNCTIONS
1549
#undef SPIRV_CROSS_MSL_FUNCTIONS
1550
#undef SPIRV_CROSS_FUNCTIONS
1551
#undef SHADERC_FUNCTIONS
1552
1553
std::optional<DynamicHeapArray<u8>> GPUDevice::OptimizeVulkanSpv(const std::span<const u8> spirv, Error* error)
1554
{
1555
std::optional<DynamicHeapArray<u8>> ret;
1556
1557
if (spirv.size() < sizeof(u32) * 2)
1558
{
1559
Error::SetStringView(error, "Invalid SPIR-V input size.");
1560
return ret;
1561
}
1562
1563
// Need to set environment based on version.
1564
u32 magic_word, spirv_version;
1565
shaderc_target_env target_env = shaderc_target_env_vulkan;
1566
shaderc_env_version target_version = shaderc_env_version_vulkan_1_0;
1567
std::memcpy(&magic_word, spirv.data(), sizeof(magic_word));
1568
std::memcpy(&spirv_version, spirv.data() + sizeof(magic_word), sizeof(spirv_version));
1569
if (magic_word != 0x07230203u)
1570
{
1571
Error::SetStringView(error, "Invalid SPIR-V magic word.");
1572
return ret;
1573
}
1574
if (spirv_version < 0x10300)
1575
target_version = shaderc_env_version_vulkan_1_0;
1576
else
1577
target_version = shaderc_env_version_vulkan_1_1;
1578
1579
if (!dyn_libs::OpenShaderc(error))
1580
return ret;
1581
1582
const shaderc_compile_options_t options = dyn_libs::shaderc_compile_options_initialize();
1583
AssertMsg(options, "shaderc_compile_options_initialize() failed");
1584
dyn_libs::shaderc_compile_options_set_target_env(options, target_env, target_version);
1585
dyn_libs::shaderc_compile_options_set_optimization_level(options, shaderc_optimization_level_performance);
1586
1587
const shaderc_compilation_result_t result =
1588
dyn_libs::shaderc_optimize_spv(dyn_libs::s_shaderc_compiler, spirv.data(), spirv.size(), options);
1589
const shaderc_compilation_status status =
1590
result ? dyn_libs::shaderc_result_get_compilation_status(result) : shaderc_compilation_status_internal_error;
1591
if (status != shaderc_compilation_status_success)
1592
{
1593
const std::string_view errors(result ? dyn_libs::shaderc_result_get_error_message(result) : "null result object");
1594
Error::SetStringFmt(error, "Failed to optimize SPIR-V: {}\n{}",
1595
dyn_libs::shaderc_compilation_status_to_string(status), errors);
1596
}
1597
else
1598
{
1599
const size_t spirv_size = dyn_libs::shaderc_result_get_length(result);
1600
DebugAssert(spirv_size > 0);
1601
ret = DynamicHeapArray<u8>(spirv_size);
1602
std::memcpy(ret->data(), dyn_libs::shaderc_result_get_bytes(result), spirv_size);
1603
}
1604
1605
dyn_libs::shaderc_result_release(result);
1606
dyn_libs::shaderc_compile_options_release(options);
1607
return ret;
1608
}
1609
1610
bool GPUDevice::CompileGLSLShaderToVulkanSpv(GPUShaderStage stage, GPUShaderLanguage source_language,
1611
std::string_view source, const char* entry_point, bool optimization,
1612
bool nonsemantic_debug_info, DynamicHeapArray<u8>* out_binary,
1613
Error* error)
1614
{
1615
static constexpr const std::array<shaderc_shader_kind, static_cast<size_t>(GPUShaderStage::MaxCount)> stage_kinds = {{
1616
shaderc_glsl_vertex_shader,
1617
shaderc_glsl_fragment_shader,
1618
shaderc_glsl_geometry_shader,
1619
shaderc_glsl_compute_shader,
1620
}};
1621
1622
if (source_language != GPUShaderLanguage::GLSLVK)
1623
{
1624
Error::SetStringFmt(error, "Unsupported source language for transpile: {}",
1625
ShaderLanguageToString(source_language));
1626
return false;
1627
}
1628
1629
if (!dyn_libs::OpenShaderc(error))
1630
return false;
1631
1632
const shaderc_compile_options_t options = dyn_libs::shaderc_compile_options_initialize();
1633
AssertMsg(options, "shaderc_compile_options_initialize() failed");
1634
1635
dyn_libs::shaderc_compile_options_set_source_language(options, shaderc_source_language_glsl);
1636
dyn_libs::shaderc_compile_options_set_target_env(options, shaderc_target_env_vulkan, 0);
1637
dyn_libs::shaderc_compile_options_set_generate_debug_info(options, m_debug_device,
1638
m_debug_device && nonsemantic_debug_info);
1639
dyn_libs::shaderc_compile_options_set_optimization_level(
1640
options, optimization ? shaderc_optimization_level_performance : shaderc_optimization_level_zero);
1641
1642
const shaderc_compilation_result_t result =
1643
dyn_libs::shaderc_compile_into_spv(dyn_libs::s_shaderc_compiler, source.data(), source.length(),
1644
stage_kinds[static_cast<size_t>(stage)], "source", entry_point, options);
1645
const shaderc_compilation_status status =
1646
result ? dyn_libs::shaderc_result_get_compilation_status(result) : shaderc_compilation_status_internal_error;
1647
if (status != shaderc_compilation_status_success)
1648
{
1649
const std::string_view errors(result ? dyn_libs::shaderc_result_get_error_message(result) : "null result object");
1650
Error::SetStringFmt(error, "Failed to compile shader to SPIR-V: {}\n{}",
1651
dyn_libs::shaderc_compilation_status_to_string(status), errors);
1652
ERROR_LOG("Failed to compile shader to SPIR-V: {}\n{}", dyn_libs::shaderc_compilation_status_to_string(status),
1653
errors);
1654
DumpBadShader(source, errors);
1655
}
1656
else
1657
{
1658
const size_t num_warnings = dyn_libs::shaderc_result_get_num_warnings(result);
1659
if (num_warnings > 0)
1660
WARNING_LOG("Shader compiled with warnings:\n{}", dyn_libs::shaderc_result_get_error_message(result));
1661
1662
const size_t spirv_size = dyn_libs::shaderc_result_get_length(result);
1663
DebugAssert(spirv_size > 0);
1664
out_binary->resize(spirv_size);
1665
std::memcpy(out_binary->data(), dyn_libs::shaderc_result_get_bytes(result), spirv_size);
1666
}
1667
1668
dyn_libs::shaderc_result_release(result);
1669
dyn_libs::shaderc_compile_options_release(options);
1670
return (status == shaderc_compilation_status_success);
1671
}
1672
1673
bool GPUDevice::TranslateVulkanSpvToLanguage(const std::span<const u8> spirv, GPUShaderStage stage,
1674
GPUShaderLanguage target_language, u32 target_version, std::string* output,
1675
Error* error)
1676
{
1677
if (!dyn_libs::OpenSpirvCross(error))
1678
return false;
1679
1680
spvc_context sctx;
1681
spvc_result sres;
1682
if ((sres = dyn_libs::spvc_context_create(&sctx)) != SPVC_SUCCESS)
1683
{
1684
Error::SetStringFmt(error, "spvc_context_create() failed: {}", static_cast<int>(sres));
1685
return false;
1686
}
1687
1688
const ScopedGuard sctx_guard = [&sctx]() { dyn_libs::spvc_context_destroy(sctx); };
1689
1690
dyn_libs::spvc_context_set_error_callback(
1691
sctx,
1692
[](void* error, const char* errormsg) {
1693
ERROR_LOG("SPIRV-Cross reported an error: {}", errormsg);
1694
Error::SetStringView(static_cast<Error*>(error), errormsg);
1695
},
1696
error);
1697
1698
spvc_parsed_ir sir;
1699
if ((sres = dyn_libs::spvc_context_parse_spirv(sctx, reinterpret_cast<const u32*>(spirv.data()), spirv.size() / 4,
1700
&sir)) != SPVC_SUCCESS)
1701
{
1702
Error::SetStringFmt(error, "spvc_context_parse_spirv() failed: {}", static_cast<int>(sres));
1703
return {};
1704
}
1705
1706
static constexpr std::array<spvc_backend, static_cast<size_t>(GPUShaderLanguage::Count)> backends = {
1707
{SPVC_BACKEND_NONE, SPVC_BACKEND_HLSL, SPVC_BACKEND_GLSL, SPVC_BACKEND_GLSL, SPVC_BACKEND_GLSL, SPVC_BACKEND_MSL,
1708
SPVC_BACKEND_NONE}};
1709
1710
spvc_compiler scompiler;
1711
if ((sres = dyn_libs::spvc_context_create_compiler(sctx, backends[static_cast<size_t>(target_language)], sir,
1712
SPVC_CAPTURE_MODE_TAKE_OWNERSHIP, &scompiler)) != SPVC_SUCCESS)
1713
{
1714
Error::SetStringFmt(error, "spvc_context_create_compiler() failed: {}", static_cast<int>(sres));
1715
return {};
1716
}
1717
1718
spvc_compiler_options soptions;
1719
if ((sres = dyn_libs::spvc_compiler_create_compiler_options(scompiler, &soptions)) != SPVC_SUCCESS)
1720
{
1721
Error::SetStringFmt(error, "spvc_compiler_create_compiler_options() failed: {}", static_cast<int>(sres));
1722
return {};
1723
}
1724
1725
spvc_resources resources;
1726
if ((sres = dyn_libs::spvc_compiler_create_shader_resources(scompiler, &resources)) != SPVC_SUCCESS)
1727
{
1728
Error::SetStringFmt(error, "spvc_compiler_create_shader_resources() failed: {}", static_cast<int>(sres));
1729
return {};
1730
}
1731
1732
// Need to know if there's UBOs for mapping.
1733
const spvc_reflected_resource *ubos, *textures;
1734
size_t ubos_count, textures_count, images_count;
1735
if ((sres = dyn_libs::spvc_resources_get_resource_list_for_type(resources, SPVC_RESOURCE_TYPE_UNIFORM_BUFFER, &ubos,
1736
&ubos_count)) != SPVC_SUCCESS ||
1737
(sres = dyn_libs::spvc_resources_get_resource_list_for_type(resources, SPVC_RESOURCE_TYPE_SAMPLED_IMAGE,
1738
&textures, &textures_count)) != SPVC_SUCCESS ||
1739
(sres = dyn_libs::spvc_resources_get_resource_list_for_type(resources, SPVC_RESOURCE_TYPE_STORAGE_IMAGE,
1740
&textures, &images_count)) != SPVC_SUCCESS)
1741
{
1742
Error::SetStringFmt(error, "spvc_resources_get_resource_list_for_type() failed: {}", static_cast<int>(sres));
1743
return {};
1744
}
1745
1746
[[maybe_unused]] const SpvExecutionModel execmodel = dyn_libs::spvc_compiler_get_execution_model(scompiler);
1747
[[maybe_unused]] static constexpr u32 UBO_DESCRIPTOR_SET = 0;
1748
[[maybe_unused]] static constexpr u32 TEXTURE_DESCRIPTOR_SET = 1;
1749
[[maybe_unused]] static constexpr u32 IMAGE_DESCRIPTOR_SET = 2;
1750
1751
switch (target_language)
1752
{
1753
#ifdef _WIN32
1754
case GPUShaderLanguage::HLSL:
1755
{
1756
if ((sres = dyn_libs::spvc_compiler_options_set_uint(soptions, SPVC_COMPILER_OPTION_HLSL_SHADER_MODEL,
1757
target_version)) != SPVC_SUCCESS)
1758
{
1759
Error::SetStringFmt(error, "spvc_compiler_options_set_uint(SPVC_COMPILER_OPTION_HLSL_SHADER_MODEL) failed: {}",
1760
static_cast<int>(sres));
1761
return {};
1762
}
1763
1764
if ((sres = dyn_libs::spvc_compiler_options_set_bool(
1765
soptions, SPVC_COMPILER_OPTION_HLSL_SUPPORT_NONZERO_BASE_VERTEX_BASE_INSTANCE, false)) != SPVC_SUCCESS)
1766
{
1767
Error::SetStringFmt(error,
1768
"spvc_compiler_options_set_bool(SPVC_COMPILER_OPTION_HLSL_SUPPORT_NONZERO_BASE_VERTEX_"
1769
"BASE_INSTANCE) failed: {}",
1770
static_cast<int>(sres));
1771
return {};
1772
}
1773
1774
if ((sres = dyn_libs::spvc_compiler_options_set_bool(soptions, SPVC_COMPILER_OPTION_HLSL_POINT_SIZE_COMPAT,
1775
true)) != SPVC_SUCCESS)
1776
{
1777
Error::SetStringFmt(error,
1778
"spvc_compiler_options_set_bool(SPVC_COMPILER_OPTION_HLSL_POINT_SIZE_COMPAT) failed: {}",
1779
static_cast<int>(sres));
1780
return {};
1781
}
1782
1783
if (ubos_count > 0)
1784
{
1785
const spvc_hlsl_resource_binding rb = {.stage = execmodel,
1786
.desc_set = UBO_DESCRIPTOR_SET,
1787
.binding = 0,
1788
.cbv = {.register_space = 0, .register_binding = 0},
1789
.uav = {},
1790
.srv = {},
1791
.sampler = {}};
1792
if ((sres = dyn_libs::spvc_compiler_hlsl_add_resource_binding(scompiler, &rb)) != SPVC_SUCCESS)
1793
{
1794
Error::SetStringFmt(error, "spvc_compiler_hlsl_add_resource_binding() failed: {}", static_cast<int>(sres));
1795
return {};
1796
}
1797
}
1798
1799
if (textures_count > 0)
1800
{
1801
for (u32 i = 0; i < textures_count; i++)
1802
{
1803
const spvc_hlsl_resource_binding rb = {.stage = execmodel,
1804
.desc_set = TEXTURE_DESCRIPTOR_SET,
1805
.binding = i,
1806
.cbv = {},
1807
.uav = {},
1808
.srv = {.register_space = 0, .register_binding = i},
1809
.sampler = {.register_space = 0, .register_binding = i}};
1810
if ((sres = dyn_libs::spvc_compiler_hlsl_add_resource_binding(scompiler, &rb)) != SPVC_SUCCESS)
1811
{
1812
Error::SetStringFmt(error, "spvc_compiler_hlsl_add_resource_binding() failed: {}", static_cast<int>(sres));
1813
return {};
1814
}
1815
}
1816
}
1817
1818
if (stage == GPUShaderStage::Compute)
1819
{
1820
for (u32 i = 0; i < images_count; i++)
1821
{
1822
const spvc_hlsl_resource_binding rb = {.stage = execmodel,
1823
.desc_set = IMAGE_DESCRIPTOR_SET,
1824
.binding = i,
1825
.cbv = {},
1826
.uav = {.register_space = 0, .register_binding = i},
1827
.srv = {},
1828
.sampler = {}};
1829
if ((sres = dyn_libs::spvc_compiler_hlsl_add_resource_binding(scompiler, &rb)) != SPVC_SUCCESS)
1830
{
1831
Error::SetStringFmt(error, "spvc_compiler_hlsl_add_resource_binding() failed: {}", static_cast<int>(sres));
1832
return {};
1833
}
1834
}
1835
}
1836
}
1837
break;
1838
#endif
1839
1840
#ifdef ENABLE_OPENGL
1841
case GPUShaderLanguage::GLSL:
1842
case GPUShaderLanguage::GLSLES:
1843
{
1844
if ((sres = dyn_libs::spvc_compiler_options_set_uint(soptions, SPVC_COMPILER_OPTION_GLSL_VERSION,
1845
target_version)) != SPVC_SUCCESS)
1846
{
1847
Error::SetStringFmt(error, "spvc_compiler_options_set_uint(SPVC_COMPILER_OPTION_GLSL_VERSION) failed: {}",
1848
static_cast<int>(sres));
1849
return {};
1850
}
1851
1852
const bool is_gles = (target_language == GPUShaderLanguage::GLSLES);
1853
if ((sres = dyn_libs::spvc_compiler_options_set_bool(soptions, SPVC_COMPILER_OPTION_GLSL_ES, is_gles)) !=
1854
SPVC_SUCCESS)
1855
{
1856
Error::SetStringFmt(error, "spvc_compiler_options_set_bool(SPVC_COMPILER_OPTION_GLSL_ES) failed: {}",
1857
static_cast<int>(sres));
1858
return {};
1859
}
1860
1861
const bool enable_420pack = (is_gles ? (target_version >= 310) : (target_version >= 420));
1862
if ((sres = dyn_libs::spvc_compiler_options_set_bool(soptions, SPVC_COMPILER_OPTION_GLSL_ENABLE_420PACK_EXTENSION,
1863
enable_420pack)) != SPVC_SUCCESS)
1864
{
1865
Error::SetStringFmt(
1866
error, "spvc_compiler_options_set_bool(SPVC_COMPILER_OPTION_GLSL_ENABLE_420PACK_EXTENSION) failed: {}",
1867
static_cast<int>(sres));
1868
return {};
1869
}
1870
}
1871
break;
1872
#endif
1873
1874
#ifdef __APPLE__
1875
case GPUShaderLanguage::MSL:
1876
{
1877
if ((sres = dyn_libs::spvc_compiler_options_set_bool(
1878
soptions, SPVC_COMPILER_OPTION_MSL_PAD_FRAGMENT_OUTPUT_COMPONENTS, true)) != SPVC_SUCCESS)
1879
{
1880
Error::SetStringFmt(
1881
error, "spvc_compiler_options_set_bool(SPVC_COMPILER_OPTION_MSL_PAD_FRAGMENT_OUTPUT_COMPONENTS) failed: {}",
1882
static_cast<int>(sres));
1883
return {};
1884
}
1885
1886
if ((sres = dyn_libs::spvc_compiler_options_set_bool(soptions, SPVC_COMPILER_OPTION_MSL_FRAMEBUFFER_FETCH_SUBPASS,
1887
m_features.framebuffer_fetch)) != SPVC_SUCCESS)
1888
{
1889
Error::SetStringFmt(
1890
error, "spvc_compiler_options_set_bool(SPVC_COMPILER_OPTION_MSL_FRAMEBUFFER_FETCH_SUBPASS) failed: {}",
1891
static_cast<int>(sres));
1892
return {};
1893
}
1894
1895
if (m_features.framebuffer_fetch &&
1896
((sres = dyn_libs::spvc_compiler_options_set_uint(soptions, SPVC_COMPILER_OPTION_MSL_VERSION,
1897
target_version)) != SPVC_SUCCESS))
1898
{
1899
Error::SetStringFmt(error, "spvc_compiler_options_set_uint(SPVC_COMPILER_OPTION_MSL_VERSION) failed: {}",
1900
static_cast<int>(sres));
1901
return {};
1902
}
1903
1904
const spvc_msl_resource_binding pc_rb = {.stage = execmodel,
1905
.desc_set = SPVC_MSL_PUSH_CONSTANT_DESC_SET,
1906
.binding = SPVC_MSL_PUSH_CONSTANT_BINDING,
1907
.msl_buffer = 0,
1908
.msl_texture = 0,
1909
.msl_sampler = 0};
1910
if ((sres = dyn_libs::spvc_compiler_msl_add_resource_binding(scompiler, &pc_rb)) != SPVC_SUCCESS)
1911
{
1912
Error::SetStringFmt(error, "spvc_compiler_msl_add_resource_binding() for push constant failed: {}",
1913
static_cast<int>(sres));
1914
return {};
1915
}
1916
1917
if (stage == GPUShaderStage::Fragment || stage == GPUShaderStage::Compute)
1918
{
1919
for (u32 i = 0; i < MAX_TEXTURE_SAMPLERS; i++)
1920
{
1921
const spvc_msl_resource_binding rb = {.stage = execmodel,
1922
.desc_set = TEXTURE_DESCRIPTOR_SET,
1923
.binding = i,
1924
.msl_buffer = i,
1925
.msl_texture = i,
1926
.msl_sampler = i};
1927
1928
if ((sres = dyn_libs::spvc_compiler_msl_add_resource_binding(scompiler, &rb)) != SPVC_SUCCESS)
1929
{
1930
Error::SetStringFmt(error, "spvc_compiler_msl_add_resource_binding() failed: {}", static_cast<int>(sres));
1931
return {};
1932
}
1933
}
1934
}
1935
1936
if (stage == GPUShaderStage::Fragment && !m_features.framebuffer_fetch)
1937
{
1938
const spvc_msl_resource_binding rb = {
1939
.stage = execmodel, .desc_set = 2, .binding = 0, .msl_texture = MAX_TEXTURE_SAMPLERS};
1940
1941
if ((sres = dyn_libs::spvc_compiler_msl_add_resource_binding(scompiler, &rb)) != SPVC_SUCCESS)
1942
{
1943
Error::SetStringFmt(error, "spvc_compiler_msl_add_resource_binding() for FB failed: {}",
1944
static_cast<int>(sres));
1945
return {};
1946
}
1947
}
1948
1949
if (stage == GPUShaderStage::Compute)
1950
{
1951
for (u32 i = 0; i < MAX_IMAGE_RENDER_TARGETS; i++)
1952
{
1953
const spvc_msl_resource_binding rb = {
1954
.stage = execmodel, .desc_set = 2, .binding = i, .msl_buffer = i, .msl_texture = i, .msl_sampler = i};
1955
1956
if ((sres = dyn_libs::spvc_compiler_msl_add_resource_binding(scompiler, &rb)) != SPVC_SUCCESS)
1957
{
1958
Error::SetStringFmt(error, "spvc_compiler_msl_add_resource_binding() failed: {}", static_cast<int>(sres));
1959
return {};
1960
}
1961
}
1962
}
1963
}
1964
break;
1965
#endif
1966
1967
default:
1968
Error::SetStringFmt(error, "Unsupported target language {}.", ShaderLanguageToString(target_language));
1969
break;
1970
}
1971
1972
if ((sres = dyn_libs::spvc_compiler_install_compiler_options(scompiler, soptions)) != SPVC_SUCCESS)
1973
{
1974
Error::SetStringFmt(error, "spvc_compiler_install_compiler_options() failed: {}", static_cast<int>(sres));
1975
return false;
1976
}
1977
1978
const char* out_src;
1979
if ((sres = dyn_libs::spvc_compiler_compile(scompiler, &out_src)) != SPVC_SUCCESS)
1980
{
1981
Error::SetStringFmt(error, "spvc_compiler_compile() failed: {}", static_cast<int>(sres));
1982
return false;
1983
}
1984
1985
const size_t out_src_length = out_src ? std::strlen(out_src) : 0;
1986
if (out_src_length == 0)
1987
{
1988
Error::SetStringView(error, "Failed to compile SPIR-V to target language.");
1989
return false;
1990
}
1991
1992
output->assign(out_src, out_src_length);
1993
return true;
1994
}
1995
1996
std::unique_ptr<GPUShader> GPUDevice::TranspileAndCreateShaderFromSource(
1997
GPUShaderStage stage, GPUShaderLanguage source_language, std::string_view source, const char* entry_point,
1998
GPUShaderLanguage target_language, u32 target_version, DynamicHeapArray<u8>* out_binary, Error* error)
1999
{
2000
// Currently, entry points must be "main". TODO: rename the entry point in the SPIR-V.
2001
if (std::strcmp(entry_point, "main") != 0)
2002
{
2003
Error::SetStringView(error, "Entry point must be main.");
2004
return {};
2005
}
2006
2007
// Disable optimization when targeting OpenGL GLSL, otherwise, the name-based linking will fail.
2008
const bool optimization =
2009
(!m_debug_device && target_language != GPUShaderLanguage::GLSL && target_language != GPUShaderLanguage::GLSLES);
2010
2011
std::span<const u8> spv;
2012
DynamicHeapArray<u8> intermediate_spv;
2013
if (source_language == GPUShaderLanguage::GLSLVK)
2014
{
2015
if (!CompileGLSLShaderToVulkanSpv(stage, source_language, source, entry_point, optimization, false,
2016
&intermediate_spv, error))
2017
{
2018
return {};
2019
}
2020
2021
spv = intermediate_spv.cspan();
2022
}
2023
else if (source_language == GPUShaderLanguage::SPV)
2024
{
2025
spv = std::span<const u8>(reinterpret_cast<const u8*>(source.data()), source.size());
2026
2027
if (optimization)
2028
{
2029
Error optimize_error;
2030
std::optional<DynamicHeapArray<u8>> optimized_spv = GPUDevice::OptimizeVulkanSpv(spv, &optimize_error);
2031
if (!optimized_spv.has_value())
2032
{
2033
WARNING_LOG("Failed to optimize SPIR-V: {}", optimize_error.GetDescription());
2034
}
2035
else
2036
{
2037
DEV_LOG("SPIR-V optimized from {} bytes to {} bytes", source.length(), optimized_spv->size());
2038
intermediate_spv = std::move(optimized_spv.value());
2039
spv = intermediate_spv.cspan();
2040
}
2041
}
2042
}
2043
else
2044
{
2045
Error::SetStringFmt(error, "Unsupported source language for transpile: {}",
2046
ShaderLanguageToString(source_language));
2047
return {};
2048
}
2049
2050
std::string dest_source;
2051
if (!TranslateVulkanSpvToLanguage(spv, stage, target_language, target_version, &dest_source, error))
2052
return {};
2053
2054
#ifdef __APPLE__
2055
// MSL converter suffixes 0.
2056
if (target_language == GPUShaderLanguage::MSL)
2057
{
2058
return CreateShaderFromSource(stage, target_language, dest_source,
2059
TinyString::from_format("{}0", entry_point).c_str(), out_binary, error);
2060
}
2061
#endif
2062
2063
return CreateShaderFromSource(stage, target_language, dest_source, entry_point, out_binary, error);
2064
}
2065
2066