1
// Copyright (c) 2015- PPSSPP Project.
3
// This program is free software: you can redistribute it and/or modify
4
// it under the terms of the GNU General Public License as published by
5
// the Free Software Foundation, version 2.0 or later versions.
7
// This program is distributed in the hope that it will be useful,
8
// but WITHOUT ANY WARRANTY; without even the implied warranty of
9
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
// GNU General Public License 2.0 for more details.
12
// A copy of the GPL 2.0 should have been included with the program.
13
// If not, see http://www.gnu.org/licenses/
15
// Official git repository and contact information can be found at
16
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
24
#include "Common/Vulkan/SPIRVDisasm.h"
26
#include "base/logging.h"
27
#include "base/display.h"
28
#include "base/stringutil.h"
29
#include "image/zim_load.h"
30
#include "math/lin/matrix4x4.h"
31
#include "math/dataconv.h"
32
#include "thin3d/thin3d.h"
34
#include "Common/Vulkan/VulkanContext.h"
35
#include "Common/Vulkan/VulkanImage.h"
36
#include "Common/Vulkan/VulkanMemory.h"
38
// We use a simple descriptor set for all rendering: 1 sampler, 1 texture, 1 UBO binding point.
39
// binding 0 - uniform data
40
// binding 1 - sampler
42
// Vertex data lives in a separate namespace (location = 0, 1, etc)
44
#include "Common/Vulkan/VulkanLoader.h"
46
// This can actually be replaced with a cast as the values are in the right order.
47
static const VkCompareOp compToVK[] = {
51
VK_COMPARE_OP_LESS_OR_EQUAL,
52
VK_COMPARE_OP_GREATER,
53
VK_COMPARE_OP_NOT_EQUAL,
54
VK_COMPARE_OP_GREATER_OR_EQUAL,
59
static const VkBlendOp blendEqToGL[] = {
62
VK_BLEND_OP_REVERSE_SUBTRACT,
65
static const VkBlendFactor blendFactorToVk[] = {
68
VK_BLEND_FACTOR_SRC_COLOR,
69
VK_BLEND_FACTOR_SRC_ALPHA,
70
VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR,
71
VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
72
VK_BLEND_FACTOR_DST_COLOR,
73
VK_BLEND_FACTOR_DST_ALPHA,
74
VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR,
75
VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA,
76
VK_BLEND_FACTOR_CONSTANT_COLOR,
79
static const VkLogicOp logicOpToVK[] = {
83
VK_LOGIC_OP_COPY_INVERTED,
91
VK_LOGIC_OP_EQUIVALENT,
92
VK_LOGIC_OP_AND_REVERSE,
93
VK_LOGIC_OP_AND_INVERTED,
94
VK_LOGIC_OP_OR_REVERSE,
95
VK_LOGIC_OP_OR_INVERTED,
98
static const VkPrimitiveTopology primToVK[] = {
99
VK_PRIMITIVE_TOPOLOGY_POINT_LIST,
100
VK_PRIMITIVE_TOPOLOGY_LINE_LIST,
101
VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
104
static inline void Uint8x4ToFloat4(uint32_t u, float f[4]) {
105
f[0] = ((u >> 0) & 0xFF) * (1.0f / 255.0f);
106
f[1] = ((u >> 8) & 0xFF) * (1.0f / 255.0f);
107
f[2] = ((u >> 16) & 0xFF) * (1.0f / 255.0f);
108
f[3] = ((u >> 24) & 0xFF) * (1.0f / 255.0f);
112
class Thin3DVKBlendState : public Thin3DBlendState {
115
VkBlendOp eqCol, eqAlpha;
116
VkBlendFactor srcCol, srcAlpha, dstColor, dstAlpha;
120
void ToVulkan(VkPipelineColorBlendStateCreateInfo *info, VkPipelineColorBlendAttachmentState *attachments) {
121
memset(info, 0, sizeof(*info));
122
info->sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
123
info->attachmentCount = 1;
124
info->logicOp = logicOp;
125
info->logicOpEnable = logicEnabled;
126
attachments[0].blendEnable = blendEnabled;
127
attachments[0].colorBlendOp = eqCol;
128
attachments[0].alphaBlendOp = eqAlpha;
129
attachments[0].colorWriteMask = 0xF;
130
attachments[0].dstAlphaBlendFactor = dstAlpha;
131
attachments[0].dstColorBlendFactor = dstColor;
132
attachments[0].srcAlphaBlendFactor = srcAlpha;
133
attachments[0].srcColorBlendFactor = srcCol;
134
info->pAttachments = attachments;
138
class Thin3DVKDepthStencilState : public Thin3DDepthStencilState {
140
bool depthTestEnabled;
141
bool depthWriteEnabled;
142
VkCompareOp depthComp;
144
void ToVulkan(VkPipelineDepthStencilStateCreateInfo *info) {
145
memset(info, 0, sizeof(*info));
146
info->sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
147
info->depthCompareOp = depthComp;
148
info->depthTestEnable = depthTestEnabled;
149
info->depthWriteEnable = depthWriteEnabled;
150
info->stencilTestEnable = false;
151
info->depthBoundsTestEnable = false;
155
// Very simplistic buffer that will simply copy its contents into our "pushbuffer" when it's time to draw,
156
// to avoid synchronization issues.
157
class Thin3DVKBuffer : public Thin3DBuffer {
159
Thin3DVKBuffer(size_t size, uint32_t flags) : dataSize_(size) {
160
data_ = new uint8_t[size];
162
~Thin3DVKBuffer() override {
166
void SetData(const uint8_t *data, size_t size) override {
169
data_ = new uint8_t[size];
171
memcpy(data_, data, size);
175
void SubData(const uint8_t *data, size_t offset, size_t size) override {
176
memcpy(data_, data_ + offset, size);
179
size_t GetSize() const { return dataSize_; }
180
const uint8_t *GetData() const { return data_; }
187
// Not registering this as a resource holder, instead ShaderSet is registered. It will
188
// invoke Compile again to recreate the shader then link them together.
189
class Thin3DVKShader : public Thin3DShader {
191
Thin3DVKShader(bool isFragmentShader) : module_(VK_NULL_HANDLE), ok_(false) {
192
stage_ = isFragmentShader ? VK_SHADER_STAGE_FRAGMENT_BIT : VK_SHADER_STAGE_VERTEX_BIT;
194
bool Compile(VulkanContext *vulkan, const char *source);
195
const std::string &GetSource() const { return source_; }
198
vkDestroyShaderModule(device_, module_, nullptr);
201
VkShaderModule Get() const { return module_; }
205
VkShaderModule module_;
206
VkShaderStageFlagBits stage_;
208
std::string source_; // So we can recompile in case of context loss.
211
bool Thin3DVKShader::Compile(VulkanContext *vulkan, const char *source) {
212
// We'll need this to free it later.
213
device_ = vulkan->GetDevice();
214
this->source_ = source;
215
std::vector<uint32_t> spirv;
216
if (!GLSLtoSPV(stage_, source, spirv)) {
220
// Just for kicks, sanity check the SPIR-V. The disasm isn't perfect
221
// but gives you some idea of what's going on.
224
if (DisassembleSPIRV(spirv, &disasm)) {
225
OutputDebugStringA(disasm.c_str());
229
if (vulkan->CreateShaderModule(spirv, &module_)) {
239
inline VkFormat ConvertVertexDataTypeToVk(T3DVertexDataType type) {
241
case FLOATx2: return VK_FORMAT_R32G32_SFLOAT;
242
case FLOATx3: return VK_FORMAT_R32G32B32_SFLOAT;
243
case FLOATx4: return VK_FORMAT_R32G32B32A32_SFLOAT;
244
case UNORM8x4: return VK_FORMAT_R8G8B8A8_UNORM;
245
default: return VK_FORMAT_UNDEFINED;
249
class Thin3DVKVertexFormat : public Thin3DVertexFormat {
251
void ToVulkan(VkPipelineVertexInputStateCreateInfo *info, VkVertexInputAttributeDescription *attrDescs, VkVertexInputBindingDescription *bindDescs) {
252
memset(info, 0, sizeof(*info));
253
info->sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
254
for (uint32_t i = 0; i < components_.size(); i++) {
255
attrDescs[i].binding = 0;
256
attrDescs[i].format = ConvertVertexDataTypeToVk(components_[i].type);
257
attrDescs[i].location = (int)components_[i].semantic;
258
attrDescs[i].offset = components_[i].offset;
260
bindDescs[0].binding = 0;
261
bindDescs[0].inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
262
bindDescs[0].stride = stride_;
264
info->vertexAttributeDescriptionCount = (uint32_t)components_.size();
265
info->pVertexAttributeDescriptions = attrDescs;
266
info->vertexBindingDescriptionCount = 1;
267
info->pVertexBindingDescriptions = bindDescs;
271
bool RequiresBuffer() {
275
std::vector<Thin3DVertexComponent> components_;
279
class Thin3DVKShaderSet : public Thin3DShaderSet {
281
Thin3DVKShaderSet() {
283
uboSize_ = 16 * sizeof(float); // WorldViewProj
284
ubo_ = new uint8_t[uboSize_];
286
~Thin3DVKShaderSet() {
293
// Returns the binding offset, and the VkBuffer to bind.
294
size_t PushUBO(VulkanPushBuffer *buf, VulkanContext *vulkan, VkBuffer *vkbuf) {
295
return buf->PushAligned(ubo_, uboSize_, vulkan->GetPhysicalDeviceProperties().limits.minUniformBufferOffsetAlignment, vkbuf);
298
int GetUniformLoc(const char *name);
300
void SetVector(const char *name, float *value, int n) override;
301
void SetMatrix4x4(const char *name, const float value[16]) override;
303
int GetUBOSize() const {
307
Thin3DVKShader *vshader;
308
Thin3DVKShader *fshader;
316
Thin3DVKDepthStencilState *depthStencil;
317
Thin3DVKBlendState *blend;
318
Thin3DVKShaderSet *shaderSet;
319
VkPrimitiveTopology topology;
320
T3DCullMode cullMode;
324
bool operator < (const PipelineKey &other) const {
325
if (depthStencil < other.depthStencil) return true; else if (depthStencil > other.depthStencil) return false;
326
if (blend < other.blend) return true; else if (blend > other.blend) return false;
327
if (shaderSet < other.shaderSet) return true; else if (shaderSet > other.shaderSet) return false;
328
if (topology < other.topology) return true; else if (topology > other.topology) return false;
329
if (cullMode < other.cullMode) return true; else if (cullMode > other.cullMode) return false;
337
class Thin3DVKTexture;
338
class Thin3DVKSamplerState;
340
struct DescriptorSetKey {
341
Thin3DVKTexture *texture_;
342
Thin3DVKSamplerState *sampler_;
345
bool operator < (const DescriptorSetKey &other) const {
346
if (texture_ < other.texture_) return true; else if (texture_ > other.texture_) return false;
347
if (sampler_ < other.sampler_) return true; else if (sampler_ > other.sampler_) return false;
348
if (buffer_ < other.buffer_) return true; else if (buffer_ > other.buffer_) return false;
353
class Thin3DVKContext : public Thin3DContext {
355
Thin3DVKContext(VulkanContext *vulkan);
356
virtual ~Thin3DVKContext();
358
Thin3DDepthStencilState *CreateDepthStencilState(bool depthTestEnabled, bool depthWriteEnabled, T3DComparison depthCompare) override;
359
Thin3DBlendState *CreateBlendState(const T3DBlendStateDesc &desc) override;
360
Thin3DBuffer *CreateBuffer(size_t size, uint32_t usageFlags) override;
361
Thin3DShaderSet *CreateShaderSet(Thin3DShader *vshader, Thin3DShader *fshader) override;
362
Thin3DVertexFormat *CreateVertexFormat(const std::vector<Thin3DVertexComponent> &components, int stride, Thin3DShader *vshader) override;
363
Thin3DSamplerState *CreateSamplerState(const T3DSamplerStateDesc &desc) override;
364
Thin3DTexture *CreateTexture(T3DTextureType type, T3DImageFormat format, int width, int height, int depth, int mipLevels) override;
365
Thin3DTexture *CreateTexture() override;
367
// Bound state objects
368
void SetBlendState(Thin3DBlendState *state) override {
369
Thin3DVKBlendState *s = static_cast<Thin3DVKBlendState *>(state);
373
// Bound state objects
374
void SetDepthStencilState(Thin3DDepthStencilState *state) override {
375
Thin3DVKDepthStencilState *s = static_cast<Thin3DVKDepthStencilState *>(state);
376
curDepthStencilState_ = s;
379
// The implementation makes the choice of which shader code to use.
380
Thin3DShader *CreateVertexShader(const char *glsl_source, const char *hlsl_source, const char *vulkan_source) override;
381
Thin3DShader *CreateFragmentShader(const char *glsl_source, const char *hlsl_source, const char *vulkan_source) override;
383
void SetScissorEnabled(bool enable) override {
384
scissorEnabled_ = enable;
385
scissorDirty_ = true;
388
void SetScissorRect(int left, int top, int width, int height) override;
390
void SetViewports(int count, T3DViewport *viewports) override;
392
void SetTextures(int start, int count, Thin3DTexture **textures) override;
394
void SetSamplerStates(int start, int count, Thin3DSamplerState **state) override;
396
void SetRenderState(T3DRenderState rs, uint32_t value) override;
398
// TODO: Add more sophisticated draws.
399
void Draw(T3DPrimitive prim, Thin3DShaderSet *shaderSet, Thin3DVertexFormat *format, Thin3DBuffer *vdata, int vertexCount, int offset) override;
400
void DrawIndexed(T3DPrimitive prim, Thin3DShaderSet *shaderSet, Thin3DVertexFormat *format, Thin3DBuffer *vdata, Thin3DBuffer *idata, int vertexCount, int offset) override;
401
void DrawUP(T3DPrimitive prim, Thin3DShaderSet *shaderSet, Thin3DVertexFormat *format, const void *vdata, int vertexCount) override;
403
void Clear(int mask, uint32_t colorval, float depthVal, int stencilVal) override;
405
void Begin(bool clear, uint32_t colorval, float depthVal, int stencilVal) override;
408
std::string GetInfoString(T3DInfo info) const override {
409
// TODO: Make these actually query the right information
411
case APINAME: return "Vulkan";
412
case VENDORSTRING: return vulkan_->GetPhysicalDeviceProperties().deviceName;
413
case VENDOR: return StringFromFormat("%08x", vulkan_->GetPhysicalDeviceProperties().vendorID);
414
case RENDERER: return StringFromFormat("%08x", vulkan_->GetPhysicalDeviceProperties().driverVersion);
415
case SHADELANGVERSION: return "N/A";;
418
uint32_t ver = vulkan_->GetPhysicalDeviceProperties().apiVersion;
419
return StringFromFormat("%d.%d.%d", ver >> 22, (ver >> 12) & 0x3ff, ver & 0xfff);
425
VkPipeline GetOrCreatePipeline();
426
VkDescriptorSet GetOrCreateDescriptorSet(VkBuffer buffer);
428
std::vector<std::string> GetFeatureList() override;
431
void ApplyDynamicState();
432
void DirtyDynamicState();
434
VulkanContext *vulkan_;
436
// These are used to compose the pipeline cache key.
437
Thin3DVKBlendState *curBlendState_;
438
Thin3DVKDepthStencilState *curDepthStencilState_;
439
Thin3DVKShaderSet *curShaderSet_;
440
VkPrimitiveTopology curPrim_;
441
Thin3DVKVertexFormat *curVertexFormat_;
442
T3DCullMode curCullMode_;
444
// We keep a pipeline state cache.
445
std::map<PipelineKey, VkPipeline> pipelines_;
447
VkDescriptorSetLayout descriptorSetLayout_;
448
VkPipelineLayout pipelineLayout_;
449
VkPipelineCache pipelineCache_;
451
VkCommandPool cmdPool_;
454
int queueFamilyIndex_;
456
// State to apply at the next draw call if viewportDirty or scissorDirty are true.
458
VkViewport viewport_;
461
bool scissorEnabled_;
463
VkRect2D noScissor_; // Simply a scissor covering the screen.
465
enum {MAX_BOUND_TEXTURES = 1};
466
Thin3DVKTexture *boundTextures_[MAX_BOUND_TEXTURES];
467
Thin3DVKSamplerState *boundSamplers_[MAX_BOUND_TEXTURES];
469
VkCommandBuffer cmd_; // The current one
472
VulkanPushBuffer *pushBuffer;
474
// Per-frame descriptor set cache. As it's per frame and reset every frame, we don't need to
475
// worry about invalidating descriptors pointing to deleted textures.
476
std::map<DescriptorSetKey, VkDescriptorSet> descSets_;
477
VkDescriptorPool descriptorPool;
483
VulkanPushBuffer *push_;
486
VkFormat FormatToVulkan(T3DImageFormat fmt, int *bpp) {
488
case RGBA8888: *bpp = 32; return VK_FORMAT_R8G8B8A8_UNORM;
489
case RGBA4444: *bpp = 16; return VK_FORMAT_R4G4B4A4_UNORM_PACK16;
490
case D24S8: *bpp = 32; return VK_FORMAT_D24_UNORM_S8_UINT;
491
case D16: *bpp = 16; return VK_FORMAT_D16_UNORM;
492
default: return VK_FORMAT_UNDEFINED;
496
class Thin3DVKSamplerState : public Thin3DSamplerState {
498
Thin3DVKSamplerState(VulkanContext *vulkan, const T3DSamplerStateDesc &desc) : vulkan_(vulkan) {
499
VkSamplerCreateInfo s = { VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO };
500
s.addressModeU = desc.wrapS ? VK_SAMPLER_ADDRESS_MODE_REPEAT : VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
501
s.addressModeV = desc.wrapT ? VK_SAMPLER_ADDRESS_MODE_REPEAT : VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
502
s.magFilter = desc.magFilt == T3DTextureFilter::LINEAR ? VK_FILTER_LINEAR : VK_FILTER_NEAREST;
503
s.minFilter = desc.minFilt == T3DTextureFilter::LINEAR ? VK_FILTER_LINEAR : VK_FILTER_NEAREST;
504
s.mipmapMode = desc.mipFilt == T3DTextureFilter::LINEAR ? VK_SAMPLER_MIPMAP_MODE_LINEAR : VK_SAMPLER_MIPMAP_MODE_NEAREST;
505
s.maxLod = 0.0; // TODO: Actually support mipmaps
507
VkResult res = vkCreateSampler(vulkan_->GetDevice(), &s, nullptr, &sampler_);
508
assert(VK_SUCCESS == res);
510
~Thin3DVKSamplerState() {
511
vkDestroySampler(vulkan_->GetDevice(), sampler_, nullptr);
514
VkSampler GetSampler() { return sampler_; }
517
VulkanContext *vulkan_;
521
Thin3DSamplerState *Thin3DVKContext::CreateSamplerState(const T3DSamplerStateDesc &desc) {
522
return new Thin3DVKSamplerState(vulkan_, desc);
525
void Thin3DVKContext::SetSamplerStates(int start, int count, Thin3DSamplerState **state) {
526
for (int i = start; i < start + count; i++) {
527
boundSamplers_[i] = (Thin3DVKSamplerState *)state[i];
531
enum class TextureState {
538
class Thin3DVKTexture : public Thin3DTexture {
540
Thin3DVKTexture(VulkanContext *vulkan) : vulkan_(vulkan), vkTex_(nullptr) {
543
Thin3DVKTexture(VulkanContext *vulkan, T3DTextureType type, T3DImageFormat format, int width, int height, int depth, int mipLevels)
544
: vulkan_(vulkan), format_(format), mipLevels_(mipLevels) {
545
Create(type, format, width, height, depth, mipLevels);
552
bool Create(T3DTextureType type, T3DImageFormat format, int width, int height, int depth, int mipLevels) override {
554
mipLevels_ = mipLevels;
558
vkTex_ = new VulkanTexture(vulkan_);
559
// We don't actually do anything here.
563
void SetImageData(int x, int y, int z, int width, int height, int depth, int level, int stride, const uint8_t *data) override;
564
void Finalize(int zim_flags) override;
565
void AutoGenMipmaps() override {}
567
VkImageView GetImageView() { return vkTex_->GetImageView(); }
577
VulkanContext *vulkan_;
578
VulkanTexture *vkTex_;
582
T3DImageFormat format_;
585
Thin3DVKContext::Thin3DVKContext(VulkanContext *vulkan)
586
: viewportDirty_(false), scissorDirty_(false), vulkan_(vulkan), frameNum_(0) {
587
device_ = vulkan->GetDevice();
589
queue_ = vulkan->GetGraphicsQueue();
590
queueFamilyIndex_ = vulkan->GetGraphicsQueueFamilyIndex();
591
noScissor_.offset.x = 0;
592
noScissor_.offset.y = 0;
593
noScissor_.extent.width = pixel_xres;
594
noScissor_.extent.height = pixel_yres;
595
scissor_ = noScissor_;
598
viewport_.width = pixel_xres;
599
viewport_.height = pixel_yres;
600
viewport_.minDepth = 0.0f;
601
viewport_.maxDepth = 0.0f;
602
memset(boundTextures_, 0, sizeof(boundTextures_));
605
VkCommandPoolCreateInfo p = { VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO };
606
p.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
607
p.queueFamilyIndex = vulkan->GetGraphicsQueueFamilyIndex();
608
VkResult res = vkCreateCommandPool(device_, &p, nullptr, &cmdPool_);
609
assert(VK_SUCCESS == res);
611
VkDescriptorPoolSize dpTypes[2];
612
dpTypes[0].descriptorCount = 200;
613
dpTypes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
614
dpTypes[1].descriptorCount = 200;
615
dpTypes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
617
VkDescriptorPoolCreateInfo dp = { VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO };
618
dp.flags = 0; // Don't want to mess around with individually freeing these, let's go dynamic each frame.
619
dp.maxSets = 200; // 200 textures per frame should be enough for the UI...
620
dp.pPoolSizes = dpTypes;
621
dp.poolSizeCount = ARRAY_SIZE(dpTypes);
622
res = vkCreateDescriptorPool(device_, &dp, nullptr, &frame_[0].descriptorPool);
623
assert(VK_SUCCESS == res);
624
res = vkCreateDescriptorPool(device_, &dp, nullptr, &frame_[1].descriptorPool);
625
assert(VK_SUCCESS == res);
627
frame_[0].pushBuffer = new VulkanPushBuffer(vulkan_, 1024 * 1024);
628
frame_[1].pushBuffer = new VulkanPushBuffer(vulkan_, 1024 * 1024);
630
// binding 0 - uniform data
631
// binding 1 - combined sampler/image
632
VkDescriptorSetLayoutBinding bindings[2];
633
bindings[0].descriptorCount = 1;
634
bindings[0].pImmutableSamplers = nullptr;
635
bindings[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
636
bindings[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
637
bindings[0].binding = 0;
638
bindings[1].descriptorCount = 1;
639
bindings[1].pImmutableSamplers = nullptr;
640
bindings[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
641
bindings[1].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
642
bindings[1].binding = 1;
644
VkDescriptorSetLayoutCreateInfo dsl = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO };
645
dsl.bindingCount = 2;
646
dsl.pBindings = bindings;
647
res = vkCreateDescriptorSetLayout(device_, &dsl, nullptr, &descriptorSetLayout_);
648
assert(VK_SUCCESS == res);
650
VkPipelineLayoutCreateInfo pl = { VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO };
651
pl.pPushConstantRanges = nullptr;
652
pl.pushConstantRangeCount = 0;
653
pl.setLayoutCount = 1;
654
pl.pSetLayouts = &descriptorSetLayout_;
655
res = vkCreatePipelineLayout(device_, &pl, nullptr, &pipelineLayout_);
656
assert(VK_SUCCESS == res);
658
pipelineCache_ = vulkan_->CreatePipelineCache();
661
Thin3DVKContext::~Thin3DVKContext() {
662
for (auto x : pipelines_) {
663
vkDestroyPipeline(device_, x.second, nullptr);
665
vkDestroyCommandPool(device_, cmdPool_, nullptr);
666
// This also destroys all descriptor sets.
667
for (int i = 0; i < 2; i++) {
668
frame_[i].descSets_.clear();
669
vkDestroyDescriptorPool(device_, frame_[i].descriptorPool, nullptr);
670
frame_[i].pushBuffer->Destroy(vulkan_);
671
delete frame_[i].pushBuffer;
673
vkDestroyDescriptorSetLayout(device_, descriptorSetLayout_, nullptr);
674
vkDestroyPipelineLayout(device_, pipelineLayout_, nullptr);
675
vkDestroyPipelineCache(device_, pipelineCache_, nullptr);
678
void Thin3DVKContext::Begin(bool clear, uint32_t colorval, float depthVal, int stencilVal) {
679
VkClearValue clearVal[2] = {};
680
Uint8x4ToFloat4(colorval, clearVal[0].color.float32);
682
// // Debug flicker - used to see if we swap at all. no longer necessary
683
// if (frameNum_ & 1)
684
// clearVal[0].color.float32[2] = 1.0f;
686
clearVal[1].depthStencil.depth = depthVal;
687
clearVal[1].depthStencil.stencil = stencilVal;
689
cmd_ = vulkan_->BeginSurfaceRenderPass(clearVal);
691
FrameData *frame = &frame_[frameNum_ & 1];
692
push_ = frame->pushBuffer;
694
// OK, we now know that nothing is reading from this frame's data pushbuffer,
696
push_->Begin(vulkan_);
698
frame->descSets_.clear();
699
VkResult result = vkResetDescriptorPool(device_, frame->descriptorPool, 0);
700
assert(result == VK_SUCCESS);
702
noScissor_.extent.width = pixel_xres;
703
noScissor_.extent.height = pixel_yres;
704
scissorDirty_ = true;
705
viewportDirty_ = true;
708
void Thin3DVKContext::End() {
709
// Stop collecting data in the frame's data pushbuffer.
711
vulkan_->EndSurfaceRenderPass();
714
cmd_ = nullptr; // will be set on the next begin
720
VkDescriptorSet Thin3DVKContext::GetOrCreateDescriptorSet(VkBuffer buf) {
721
DescriptorSetKey key;
723
FrameData *frame = &frame_[frameNum_ & 1];
725
key.texture_ = boundTextures_[0];
726
key.sampler_ = boundSamplers_[0];
729
auto iter = frame->descSets_.find(key);
730
if (iter != frame->descSets_.end()) {
734
VkDescriptorSet descSet;
735
VkDescriptorSetAllocateInfo alloc = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO };
736
alloc.descriptorPool = frame->descriptorPool;
737
alloc.pSetLayouts = &descriptorSetLayout_;
738
alloc.descriptorSetCount = 1;
739
VkResult res = vkAllocateDescriptorSets(device_, &alloc, &descSet);
740
assert(VK_SUCCESS == res);
742
VkDescriptorBufferInfo bufferDesc;
743
bufferDesc.buffer = buf;
744
bufferDesc.offset = 0;
745
bufferDesc.range = curShaderSet_->GetUBOSize();
747
VkDescriptorImageInfo imageDesc;
748
imageDesc.imageView = boundTextures_[0]->GetImageView();
749
imageDesc.sampler = boundSamplers_[0]->GetSampler();
750
imageDesc.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
752
VkWriteDescriptorSet writes[2] = {};
753
writes[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
754
writes[0].dstSet = descSet;
755
writes[0].dstArrayElement = 0;
756
writes[0].dstBinding = 0;
757
writes[0].pBufferInfo = &bufferDesc;
758
writes[0].pImageInfo = nullptr;
759
writes[0].pTexelBufferView = nullptr;
760
writes[0].descriptorCount = 1;
761
writes[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
763
writes[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
764
writes[1].dstSet = descSet;
765
writes[1].dstArrayElement = 0;
766
writes[1].dstBinding = 1;
767
writes[1].pBufferInfo = nullptr;
768
writes[1].pImageInfo = &imageDesc;
769
writes[1].pTexelBufferView = nullptr;
770
writes[1].descriptorCount = 1;
771
writes[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
773
vkUpdateDescriptorSets(device_, 2, writes, 0, nullptr);
775
frame->descSets_[key] = descSet;
779
VkPipeline Thin3DVKContext::GetOrCreatePipeline() {
781
key.blend = curBlendState_;
782
key.depthStencil = curDepthStencilState_;
783
key.shaderSet = curShaderSet_;
784
key.topology = curPrim_;
785
key.cullMode = curCullMode_;
787
auto iter = pipelines_.find(key);
788
if (iter != pipelines_.end()) {
792
VkPipelineShaderStageCreateInfo stages[2];
793
stages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
794
stages[0].pNext = nullptr;
795
stages[0].pSpecializationInfo = nullptr;
796
stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
797
stages[0].module = curShaderSet_->vshader->Get();
798
stages[0].pName = "main";
801
stages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
802
stages[1].pNext = nullptr;
803
stages[1].pSpecializationInfo = nullptr;
804
stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
805
stages[1].module = curShaderSet_->fshader->Get();
806
stages[1].pName = "main";
809
VkPipelineColorBlendStateCreateInfo colorBlend;
810
VkPipelineColorBlendAttachmentState attachment0;
811
curBlendState_->ToVulkan(&colorBlend, &attachment0);
813
VkPipelineDepthStencilStateCreateInfo depthStencil;
814
curDepthStencilState_->ToVulkan(&depthStencil);
816
VkPipelineVertexInputStateCreateInfo vertex;
817
VkVertexInputAttributeDescription attrDescs[4];
818
VkVertexInputBindingDescription bindDescs[1];
819
curVertexFormat_->ToVulkan(&vertex, attrDescs, bindDescs);
821
VkPipelineInputAssemblyStateCreateInfo inputAssembly = { VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO };
822
inputAssembly.topology = curPrim_;
823
inputAssembly.primitiveRestartEnable = false;
825
VkDynamicState dynamics[] = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
826
VkPipelineDynamicStateCreateInfo dynamicInfo = { VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO };
827
dynamicInfo.dynamicStateCount = ARRAY_SIZE(dynamics);
828
dynamicInfo.pDynamicStates = dynamics;
830
VkPipelineRasterizationStateCreateInfo raster = { VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO };
831
switch (curCullMode_) {
832
case NO_CULL: raster.cullMode = VK_CULL_MODE_NONE; break;
833
case CW: raster.cullMode = VK_CULL_MODE_BACK_BIT; break;
835
case CCW: raster.cullMode = VK_CULL_MODE_FRONT_BIT; break;
837
raster.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
838
raster.polygonMode = VK_POLYGON_MODE_FILL;
839
raster.rasterizerDiscardEnable = false;
840
raster.lineWidth = 1.0f;
841
raster.depthBiasClamp = 0.0f;
842
raster.depthBiasEnable = false;
843
raster.depthClampEnable = false;
844
raster.depthBiasSlopeFactor = 0.0;
846
VkPipelineMultisampleStateCreateInfo ms = { VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO };
848
ms.pSampleMask = nullptr;
849
ms.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
851
VkPipelineViewportStateCreateInfo vs = { VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO };
853
vs.viewportCount = 1;
855
vs.pViewports = nullptr; // dynamic
856
vs.pScissors = nullptr; // dynamic
858
VkGraphicsPipelineCreateInfo info = { VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO };
859
info.pNext = nullptr;
862
info.pStages = stages;
863
info.pColorBlendState = &colorBlend;
864
info.pDepthStencilState = &depthStencil;
865
info.pDynamicState = &dynamicInfo;
866
info.pInputAssemblyState = &inputAssembly;
867
info.pTessellationState = nullptr;
868
info.pMultisampleState = &ms;
869
info.pVertexInputState = &vertex;
870
info.pRasterizationState = &raster;
871
info.pViewportState = &vs; // Must set viewport and scissor counts even if we set the actual state dynamically.
872
info.layout = pipelineLayout_;
874
info.renderPass = vulkan_->GetSurfaceRenderPass();
876
// OK, need to create a new pipeline.
878
VkResult result = vkCreateGraphicsPipelines(device_, pipelineCache_, 1, &info, nullptr, &pipeline);
879
if (result != VK_SUCCESS) {
880
ELOG("Failed to create graphics pipeline");
881
return VK_NULL_HANDLE;
884
pipelines_.insert(std::make_pair(key, pipeline));
888
void Thin3DVKContext::SetScissorRect(int left, int top, int width, int height) {
889
scissor_.offset.x = left;
890
scissor_.offset.y = top;
891
scissor_.extent.width = width;
892
scissor_.extent.height = height;
893
scissorDirty_ = true;
896
void Thin3DVKContext::SetViewports(int count, T3DViewport *viewports) {
897
viewport_.x = viewports[0].TopLeftX;
898
viewport_.y = viewports[0].TopLeftY;
899
viewport_.width = viewports[0].Width;
900
viewport_.height = viewports[0].Height;
901
viewport_.minDepth = viewports[0].MinDepth;
902
viewport_.maxDepth = viewports[0].MaxDepth;
903
viewportDirty_ = true;
906
void Thin3DVKContext::ApplyDynamicState() {
908
if (scissorEnabled_) {
909
vkCmdSetScissor(cmd_, 0, 1, &scissor_);
911
vkCmdSetScissor(cmd_, 0, 1, &noScissor_);
913
scissorDirty_ = false;
915
if (viewportDirty_) {
916
vkCmdSetViewport(cmd_, 0, 1, &viewport_);
917
viewportDirty_ = false;
921
void Thin3DVKContext::DirtyDynamicState() {
922
scissorDirty_ = true;
923
viewportDirty_ = true;
926
Thin3DVertexFormat *Thin3DVKContext::CreateVertexFormat(const std::vector<Thin3DVertexComponent> &components, int stride, Thin3DShader *vshader) {
927
Thin3DVKVertexFormat *fmt = new Thin3DVKVertexFormat();
928
fmt->components_ = components;
929
fmt->stride_ = stride;
933
Thin3DTexture *Thin3DVKContext::CreateTexture() {
934
return new Thin3DVKTexture(vulkan_);
937
Thin3DTexture *Thin3DVKContext::CreateTexture(T3DTextureType type, T3DImageFormat format, int width, int height, int depth, int mipLevels) {
938
return new Thin3DVKTexture(vulkan_, type, format, width, height, depth, mipLevels);
941
void Thin3DVKTexture::SetImageData(int x, int y, int z, int width, int height, int depth, int level, int stride, const uint8_t *data) {
943
VkFormat vulkanFormat = FormatToVulkan(format_, &bpp);
944
int bytesPerPixel = bpp / 8;
945
vkTex_->Create(width, height, vulkanFormat);
947
uint8_t *dstData = vkTex_->Lock(0, &rowPitch);
948
for (int y = 0; y < height; y++) {
949
memcpy(dstData + rowPitch * y, data + stride * y, width * bytesPerPixel);
954
void Thin3DVKTexture::Finalize(int zim_flags) {
958
Thin3DDepthStencilState *Thin3DVKContext::CreateDepthStencilState(bool depthTestEnabled, bool depthWriteEnabled, T3DComparison depthCompare) {
959
Thin3DVKDepthStencilState *ds = new Thin3DVKDepthStencilState();
960
ds->depthTestEnabled = depthTestEnabled;
961
ds->depthWriteEnabled = depthWriteEnabled;
962
ds->depthComp = compToVK[depthCompare];
966
Thin3DBlendState *Thin3DVKContext::CreateBlendState(const T3DBlendStateDesc &desc) {
967
Thin3DVKBlendState *bs = new Thin3DVKBlendState();
968
bs->blendEnabled = desc.enabled;
969
bs->eqCol = blendEqToGL[desc.eqCol];
970
bs->srcCol = blendFactorToVk[desc.srcCol];
971
bs->dstColor = blendFactorToVk[desc.dstCol];
972
bs->eqAlpha = blendEqToGL[desc.eqAlpha];
973
bs->srcAlpha = blendFactorToVk[desc.srcAlpha];
974
bs->dstAlpha = blendFactorToVk[desc.dstAlpha];
975
bs->logicEnabled = desc.logicEnabled;
976
bs->logicOp = logicOpToVK[desc.logicOp];
980
Thin3DBuffer *Thin3DVKContext::CreateBuffer(size_t size, uint32_t usageFlags) {
981
return new Thin3DVKBuffer(size, usageFlags);
984
Thin3DShaderSet *Thin3DVKContext::CreateShaderSet(Thin3DShader *vshader, Thin3DShader *fshader) {
985
if (!vshader || !fshader) {
986
ELOG("ShaderSet requires both a valid vertex and a fragment shader: %p %p", vshader, fshader);
989
Thin3DVKShaderSet *shaderSet = new Thin3DVKShaderSet();
992
shaderSet->vshader = static_cast<Thin3DVKShader *>(vshader);
993
shaderSet->fshader = static_cast<Thin3DVKShader *>(fshader);
994
if (shaderSet->Link()) {
1002
void Thin3DVKContext::SetTextures(int start, int count, Thin3DTexture **textures) {
1003
for (int i = start; i < start + count; i++) {
1004
boundTextures_[i] = static_cast<Thin3DVKTexture *>(textures[i]);
1008
Thin3DShader *Thin3DVKContext::CreateVertexShader(const char *glsl_source, const char *hlsl_source, const char *vulkan_source) {
1009
Thin3DVKShader *shader = new Thin3DVKShader(false);
1010
if (shader->Compile(vulkan_, vulkan_source)) {
1013
ELOG("Failed to compile shader: %s", vulkan_source);
1019
Thin3DShader *Thin3DVKContext::CreateFragmentShader(const char *glsl_source, const char *hlsl_source, const char *vulkan_source) {
1020
Thin3DVKShader *shader = new Thin3DVKShader(true);
1021
if (shader->Compile(vulkan_, vulkan_source)) {
1024
ELOG("Failed to compile shader: %s", vulkan_source);
1030
bool Thin3DVKShaderSet::Link() {
1031
// There is no link step. However, we will create and cache Pipeline objects in the device context.
1035
int Thin3DVKShaderSet::GetUniformLoc(const char *name) {
1038
// HACK! As we only use one uniform we hardcode it.
1039
if (!strcmp(name, "WorldViewProj")) {
1046
void Thin3DVKShaderSet::SetVector(const char *name, float *value, int n) {
1050
void Thin3DVKShaderSet::SetMatrix4x4(const char *name, const float value[16]) {
1051
int loc = GetUniformLoc(name);
1053
memcpy(ubo_ + loc, value, 16 * sizeof(float));
1057
void Thin3DVKContext::SetRenderState(T3DRenderState rs, uint32_t value) {
1059
case T3DRenderState::CULL_MODE:
1060
curCullMode_ = (T3DCullMode)value;
1065
void Thin3DVKContext::Draw(T3DPrimitive prim, Thin3DShaderSet *shaderSet, Thin3DVertexFormat *format, Thin3DBuffer *vdata, int vertexCount, int offset) {
1066
ApplyDynamicState();
1068
curPrim_ = primToVK[prim];
1069
curShaderSet_ = (Thin3DVKShaderSet *)shaderSet;
1070
curVertexFormat_ = (Thin3DVKVertexFormat *)format;
1071
Thin3DVKBuffer *vbuf = static_cast<Thin3DVKBuffer *>(vdata);
1073
VkBuffer vulkanVbuf;
1074
VkBuffer vulkanUBObuf;
1075
uint32_t ubo_offset = (uint32_t)curShaderSet_->PushUBO(push_, vulkan_, &vulkanUBObuf);
1076
size_t vbBindOffset = push_->Push(vbuf->GetData(), vbuf->GetSize(), &vulkanVbuf);
1078
VkPipeline pipeline = GetOrCreatePipeline();
1079
vkCmdBindPipeline(cmd_, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
1080
VkDescriptorSet descSet = GetOrCreateDescriptorSet(vulkanUBObuf);
1081
vkCmdBindDescriptorSets(cmd_, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout_, 0, 1, &descSet, 1, &ubo_offset);
1082
VkBuffer buffers[1] = { vulkanVbuf };
1083
VkDeviceSize offsets[1] = { vbBindOffset };
1084
vkCmdBindVertexBuffers(cmd_, 0, 1, buffers, offsets);
1085
vkCmdDraw(cmd_, vertexCount, 1, offset, 0);
1088
void Thin3DVKContext::DrawIndexed(T3DPrimitive prim, Thin3DShaderSet *shaderSet, Thin3DVertexFormat *format, Thin3DBuffer *vdata, Thin3DBuffer *idata, int vertexCount, int offset) {
1089
ApplyDynamicState();
1091
curPrim_ = primToVK[prim];
1092
curShaderSet_ = (Thin3DVKShaderSet *)shaderSet;
1093
curVertexFormat_ = (Thin3DVKVertexFormat *)format;
1095
Thin3DVKBuffer *ibuf = static_cast<Thin3DVKBuffer *>(idata);
1096
Thin3DVKBuffer *vbuf = static_cast<Thin3DVKBuffer *>(vdata);
1098
VkBuffer vulkanVbuf, vulkanIbuf, vulkanUBObuf;
1099
uint32_t ubo_offset = (uint32_t)curShaderSet_->PushUBO(push_, vulkan_, &vulkanUBObuf);
1100
size_t vbBindOffset = push_->Push(vbuf->GetData(), vbuf->GetSize(), &vulkanVbuf);
1101
size_t ibBindOffset = push_->Push(ibuf->GetData(), ibuf->GetSize(), &vulkanIbuf);
1103
VkPipeline pipeline = GetOrCreatePipeline();
1104
vkCmdBindPipeline(cmd_, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
1106
VkDescriptorSet descSet = GetOrCreateDescriptorSet(vulkanUBObuf);
1107
vkCmdBindDescriptorSets(cmd_, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout_, 0, 1, &descSet, 1, &ubo_offset);
1109
VkBuffer buffers[1] = { vulkanVbuf };
1110
VkDeviceSize offsets[1] = { vbBindOffset };
1111
vkCmdBindVertexBuffers(cmd_, 0, 1, buffers, offsets);
1113
vkCmdBindIndexBuffer(cmd_, vulkanIbuf, ibBindOffset, VK_INDEX_TYPE_UINT32);
1114
vkCmdDrawIndexed(cmd_, vertexCount, 1, 0, offset, 0);
1117
void Thin3DVKContext::DrawUP(T3DPrimitive prim, Thin3DShaderSet *shaderSet, Thin3DVertexFormat *format, const void *vdata, int vertexCount) {
1118
ApplyDynamicState();
1120
curPrim_ = primToVK[prim];
1121
curShaderSet_ = (Thin3DVKShaderSet *)shaderSet;
1122
curVertexFormat_ = (Thin3DVKVertexFormat *)format;
1124
VkBuffer vulkanVbuf, vulkanUBObuf;
1125
size_t vbBindOffset = push_->Push(vdata, vertexCount * curVertexFormat_->stride_, &vulkanVbuf);
1126
uint32_t ubo_offset = (uint32_t)curShaderSet_->PushUBO(push_, vulkan_, &vulkanUBObuf);
1128
VkPipeline pipeline = GetOrCreatePipeline();
1129
vkCmdBindPipeline(cmd_, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
1131
VkBuffer buffers[1] = { vulkanVbuf };
1132
VkDeviceSize offsets[1] = { vbBindOffset };
1133
vkCmdBindVertexBuffers(cmd_, 0, 1, buffers, offsets);
1135
VkDescriptorSet descSet = GetOrCreateDescriptorSet(vulkanUBObuf);
1136
vkCmdBindDescriptorSets(cmd_, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout_, 0, 1, &descSet, 1, &ubo_offset);
1137
vkCmdDraw(cmd_, vertexCount, 1, 0, 0);
1140
void Thin3DVKContext::Clear(int mask, uint32_t colorval, float depthVal, int stencilVal) {
1141
if (mask & T3DClear::COLOR) {
1142
VkClearColorValue col;
1143
Uint8x4ToFloat4(colorval, col.float32);
1148
vkCmdClearColorAttachment(cmdBuf_, 0, imageLayout_, &col, 1, nullptr);
1151
if (mask & (T3DClear::DEPTH | T3DClear::STENCIL)) {
1156
Thin3DContext *T3DCreateVulkanContext(VulkanContext *vulkan) {
1157
return new Thin3DVKContext(vulkan);
1160
void AddFeature(std::vector<std::string> &features, const char *name, VkBool32 available, VkBool32 enabled) {
1162
snprintf(buf, sizeof(buf), "%s: Available: %d Enabled: %d", name, (int)available, (int)enabled);
1163
features.push_back(buf);
1166
std::vector<std::string> Thin3DVKContext::GetFeatureList() {
1167
const VkPhysicalDeviceFeatures &available = vulkan_->GetFeaturesAvailable();
1168
const VkPhysicalDeviceFeatures &enabled = vulkan_->GetFeaturesEnabled();
1169
std::vector<std::string> features;
1170
AddFeature(features, "dualSrcBlend", available.dualSrcBlend, enabled.dualSrcBlend);
1171
AddFeature(features, "logicOp", available.logicOp, enabled.logicOp);
1172
AddFeature(features, "geometryShader", available.geometryShader, enabled.geometryShader);
1173
AddFeature(features, "depthBounds", available.depthBounds, enabled.depthBounds);
1174
AddFeature(features, "depthClamp", available.depthClamp, enabled.depthClamp);
1175
AddFeature(features, "fillModeNonSolid", available.fillModeNonSolid, enabled.fillModeNonSolid);
1176
AddFeature(features, "largePoints", available.largePoints, enabled.largePoints);
1177
AddFeature(features, "wideLines", available.wideLines, enabled.wideLines);
1178
AddFeature(features, "pipelineStatisticsQuery", available.pipelineStatisticsQuery, enabled.pipelineStatisticsQuery);
1179
AddFeature(features, "samplerAnisotropy", available.samplerAnisotropy, enabled.samplerAnisotropy);
1180
AddFeature(features, "textureCompressionBC", available.textureCompressionBC, enabled.textureCompressionBC);
1181
AddFeature(features, "textureCompressionETC2", available.textureCompressionETC2, enabled.textureCompressionETC2);
1182
AddFeature(features, "textureCompressionASTC_LDR", available.textureCompressionASTC_LDR, enabled.textureCompressionASTC_LDR);
1183
AddFeature(features, "shaderClipDistance", available.shaderClipDistance, enabled.shaderClipDistance);
1184
AddFeature(features, "shaderCullDistance", available.shaderCullDistance, enabled.shaderCullDistance);
1185
AddFeature(features, "occlusionQueryPrecise", available.occlusionQueryPrecise, enabled.occlusionQueryPrecise);
1186
AddFeature(features, "multiDrawIndirect", available.multiDrawIndirect, enabled.multiDrawIndirect);
1188
// Also list texture formats and their properties.
1189
for (int i = VK_FORMAT_BEGIN_RANGE; i <= VK_FORMAT_END_RANGE; i++) {