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