mirror of
https://github.com/JetBrains/JetBrainsRuntime.git
synced 2026-01-08 09:31:42 +01:00
JBR-8471 Vulkan: Reuse descriptor sets in blit routines
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
#version 450
|
||||
|
||||
layout(binding = 0) uniform sampler2D u_TexSampler;
|
||||
layout(set = 0, binding = 0) uniform texture2D u_Texture;
|
||||
layout(set = 1, binding = 0) uniform sampler u_Sampler;
|
||||
layout(location = 0) in vec2 in_TexCoord;
|
||||
layout(location = 0) out vec4 out_Color;
|
||||
|
||||
void main() {
|
||||
// TODO consider in/out alpha type.
|
||||
out_Color = texture(u_TexSampler, in_TexCoord);
|
||||
out_Color = texture(sampler2D(u_Texture, u_Sampler), in_TexCoord);
|
||||
}
|
||||
|
||||
@@ -122,6 +122,7 @@ DEVICE_FUNCTION_TABLE_ENTRY(vkUpdateDescriptorSets);
|
||||
DEVICE_FUNCTION_TABLE_ENTRY(vkCreateDescriptorPool);
|
||||
DEVICE_FUNCTION_TABLE_ENTRY(vkDestroyDescriptorPool);
|
||||
DEVICE_FUNCTION_TABLE_ENTRY(vkAllocateDescriptorSets);
|
||||
DEVICE_FUNCTION_TABLE_ENTRY(vkFreeDescriptorSets);
|
||||
DEVICE_FUNCTION_TABLE_ENTRY(vkCmdBindDescriptorSets);
|
||||
DEVICE_FUNCTION_TABLE_ENTRY(vkGetImageMemoryRequirements2);
|
||||
DEVICE_FUNCTION_TABLE_ENTRY(vkCreateBuffer);
|
||||
|
||||
@@ -144,8 +144,11 @@ void VKImage_Destroy(VKDevice* device, VKImage* image) {
|
||||
if (image == NULL) return;
|
||||
if (image->viewMap != NULL) {
|
||||
for (const VKImageViewKey* k = NULL; (k = MAP_NEXT_KEY(image->viewMap, k)) != NULL;) {
|
||||
const VkImageView* view = MAP_FIND(image->viewMap, *k);
|
||||
device->vkDestroyImageView(device->handle, *view, NULL);
|
||||
const VKImageViewInfo* viewInfo = MAP_FIND(image->viewMap, *k);
|
||||
if (viewInfo->descriptorSet != VK_NULL_HANDLE) {
|
||||
device->vkFreeDescriptorSets(device->handle, viewInfo->descriptorPool, 1, &viewInfo->descriptorSet);
|
||||
}
|
||||
device->vkDestroyImageView(device->handle, viewInfo->view, NULL);
|
||||
}
|
||||
MAP_FREE(image->viewMap);
|
||||
}
|
||||
@@ -154,13 +157,38 @@ void VKImage_Destroy(VKDevice* device, VKImage* image) {
|
||||
free(image);
|
||||
}
|
||||
|
||||
VkImageView VKImage_GetView(VKDevice* device, VKImage* image, VkFormat format, VKPackedSwizzle swizzle) {
|
||||
static VKImageViewInfo* VKImage_GetViewInfo(VKDevice* device, VKImage* image, VkFormat format, VKPackedSwizzle swizzle) {
|
||||
VKImageViewKey key = { format, swizzle };
|
||||
const VkImageView* view = MAP_FIND(image->viewMap, key);
|
||||
if (view == NULL) {
|
||||
VkImageView* newView = &MAP_AT(image->viewMap, key);
|
||||
*newView = VKImage_CreateView(device, image->handle, format, VK_UNPACK_SWIZZLE(swizzle));
|
||||
view = newView;
|
||||
VKImageViewInfo* viewInfo = MAP_FIND(image->viewMap, key);
|
||||
if (viewInfo == NULL || viewInfo->view == VK_NULL_HANDLE) {
|
||||
if (viewInfo == NULL) viewInfo = &MAP_AT(image->viewMap, key);
|
||||
viewInfo->view = VKImage_CreateView(device, image->handle, format, VK_UNPACK_SWIZZLE(swizzle));
|
||||
}
|
||||
return *view;
|
||||
return viewInfo;
|
||||
}
|
||||
|
||||
VkImageView VKImage_GetView(VKDevice* device, VKImage* image, VkFormat format, VKPackedSwizzle swizzle) {
|
||||
return VKImage_GetViewInfo(device, image, format, swizzle)->view;
|
||||
}
|
||||
|
||||
VkDescriptorSet VKImage_GetDescriptorSet(VKDevice* device, VKImage* image, VkFormat format, VKPackedSwizzle swizzle) {
|
||||
VKImageViewInfo* info = VKImage_GetViewInfo(device, image, format, swizzle);
|
||||
if (info->descriptorSet == VK_NULL_HANDLE) {
|
||||
VKRenderer_CreateImageDescriptorSet(device->renderer, &info->descriptorPool, &info->descriptorSet);
|
||||
VkDescriptorImageInfo imageInfo = {
|
||||
.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
||||
.imageView = info->view
|
||||
};
|
||||
VkWriteDescriptorSet descriptorWrites = {
|
||||
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
||||
.dstSet = info->descriptorSet,
|
||||
.dstBinding = 0,
|
||||
.dstArrayElement = 0,
|
||||
.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE,
|
||||
.descriptorCount = 1,
|
||||
.pImageInfo = &imageInfo
|
||||
};
|
||||
device->vkUpdateDescriptorSets(device->handle, 1, &descriptorWrites, 0, NULL);
|
||||
}
|
||||
return info->descriptorSet;
|
||||
}
|
||||
|
||||
@@ -35,12 +35,19 @@ typedef struct {
|
||||
VKPackedSwizzle swizzle;
|
||||
} VKImageViewKey;
|
||||
|
||||
typedef struct {
|
||||
VkImageView view;
|
||||
VkDescriptorSet descriptorSet;
|
||||
VkDescriptorPool descriptorPool; // Non-owning.
|
||||
} VKImageViewInfo;
|
||||
|
||||
struct VKImage {
|
||||
VkImage handle;
|
||||
VKMemory memory;
|
||||
MAP(VKImageViewKey, VkImageView) viewMap;
|
||||
VkFormat format;
|
||||
VkExtent2D extent;
|
||||
VkImage handle;
|
||||
VKMemory memory;
|
||||
VkFormat format;
|
||||
VkExtent2D extent;
|
||||
MAP(VKImageViewKey, VKImageViewInfo) viewMap;
|
||||
|
||||
|
||||
VkImageLayout layout;
|
||||
VkPipelineStageFlagBits lastStage;
|
||||
@@ -59,4 +66,6 @@ void VKImage_Destroy(VKDevice* device, VKImage* image);
|
||||
|
||||
VkImageView VKImage_GetView(VKDevice* device, VKImage* image, VkFormat format, VKPackedSwizzle swizzle);
|
||||
|
||||
VkDescriptorSet VKImage_GetDescriptorSet(VKDevice* device, VKImage* image, VkFormat format, VKPackedSwizzle swizzle);
|
||||
|
||||
#endif // VKImage_h_Included
|
||||
|
||||
@@ -395,7 +395,7 @@ static VkResult VKPipelines_InitPipelineLayouts(VKDevice* device, VKPipelineCont
|
||||
|
||||
VkDescriptorSetLayoutBinding textureLayoutBinding = {
|
||||
.binding = 0,
|
||||
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
||||
.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE,
|
||||
.descriptorCount = 1,
|
||||
.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||
.pImmutableSamplers = NULL
|
||||
@@ -408,8 +408,27 @@ static VkResult VKPipelines_InitPipelineLayouts(VKDevice* device, VKPipelineCont
|
||||
result = device->vkCreateDescriptorSetLayout(device->handle, &textureDescriptorSetLayoutCreateInfo, NULL, &pipelines->textureDescriptorSetLayout);
|
||||
VK_IF_ERROR(result) return result;
|
||||
|
||||
createInfo.setLayoutCount = 1;
|
||||
createInfo.pSetLayouts = &pipelines->textureDescriptorSetLayout;
|
||||
VkDescriptorSetLayoutBinding samplerLayoutBinding = {
|
||||
.binding = 0,
|
||||
.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER,
|
||||
.descriptorCount = 1,
|
||||
.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||
.pImmutableSamplers = NULL
|
||||
};
|
||||
VkDescriptorSetLayoutCreateInfo samplerDescriptorSetLayoutCreateInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
|
||||
.bindingCount = 1,
|
||||
.pBindings = &samplerLayoutBinding
|
||||
};
|
||||
result = device->vkCreateDescriptorSetLayout(device->handle, &samplerDescriptorSetLayoutCreateInfo, NULL, &pipelines->samplerDescriptorSetLayout);
|
||||
VK_IF_ERROR(result) return result;
|
||||
|
||||
VkDescriptorSetLayout textureDescriptorSetLayouts[] = {
|
||||
pipelines->textureDescriptorSetLayout,
|
||||
pipelines->samplerDescriptorSetLayout
|
||||
};
|
||||
createInfo.setLayoutCount = 2;
|
||||
createInfo.pSetLayouts = textureDescriptorSetLayouts;
|
||||
result = device->vkCreatePipelineLayout(device->handle, &createInfo, NULL, &pipelines->texturePipelineLayout);
|
||||
VK_IF_ERROR(result) return result;
|
||||
|
||||
@@ -453,6 +472,22 @@ VKPipelineContext* VKPipelines_CreateContext(VKDevice* device) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Create descriptor pool.
|
||||
VkDescriptorPoolSize poolSize = {
|
||||
.type = VK_DESCRIPTOR_TYPE_SAMPLER,
|
||||
.descriptorCount = 1
|
||||
};
|
||||
VkDescriptorPoolCreateInfo poolInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
|
||||
.poolSizeCount = 1,
|
||||
.pPoolSizes = &poolSize,
|
||||
.maxSets = 1
|
||||
};
|
||||
VK_IF_ERROR(device->vkCreateDescriptorPool(device->handle, &poolInfo, NULL, &pipelineContext->samplerDescriptorPool)) {
|
||||
VKPipelines_DestroyContext(pipelineContext);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Create sampler.
|
||||
VkSamplerCreateInfo samplerCreateInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
|
||||
@@ -467,6 +502,32 @@ VKPipelineContext* VKPipelines_CreateContext(VKDevice* device) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Create sampler descriptor set.
|
||||
VkDescriptorSetAllocateInfo samplerDescriptorSetAllocateInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
|
||||
.descriptorPool = pipelineContext->samplerDescriptorPool,
|
||||
.descriptorSetCount = 1,
|
||||
.pSetLayouts = &pipelineContext->samplerDescriptorSetLayout
|
||||
};
|
||||
VK_IF_ERROR(device->vkAllocateDescriptorSets(device->handle, &samplerDescriptorSetAllocateInfo,
|
||||
&pipelineContext->linearRepeatSamplerDescriptorSet)) {
|
||||
VKPipelines_DestroyContext(pipelineContext);
|
||||
return NULL;
|
||||
}
|
||||
VkDescriptorImageInfo samplerImageInfo = {
|
||||
.sampler = pipelineContext->linearRepeatSampler
|
||||
};
|
||||
VkWriteDescriptorSet descriptorWrites = {
|
||||
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
||||
.dstSet = pipelineContext->linearRepeatSamplerDescriptorSet,
|
||||
.dstBinding = 0,
|
||||
.dstArrayElement = 0,
|
||||
.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER,
|
||||
.descriptorCount = 1,
|
||||
.pImageInfo = &samplerImageInfo
|
||||
};
|
||||
device->vkUpdateDescriptorSets(device->handle, 1, &descriptorWrites, 0, NULL);
|
||||
|
||||
J2dRlsTraceLn(J2D_TRACE_INFO, "VKPipelines_CreateContext(%p)", pipelineContext);
|
||||
return pipelineContext;
|
||||
}
|
||||
@@ -482,11 +543,13 @@ void VKPipelines_DestroyContext(VKPipelineContext* pipelineContext) {
|
||||
ARRAY_FREE(pipelineContext->renderPassContexts);
|
||||
|
||||
VKPipelines_DestroyShaders(device, pipelineContext->shaders);
|
||||
device->vkDestroyDescriptorPool(device->handle, pipelineContext->samplerDescriptorPool, NULL);
|
||||
device->vkDestroySampler(device->handle, pipelineContext->linearRepeatSampler, NULL);
|
||||
|
||||
device->vkDestroyPipelineLayout(device->handle, pipelineContext->colorPipelineLayout, NULL);
|
||||
device->vkDestroyPipelineLayout(device->handle, pipelineContext->texturePipelineLayout, NULL);
|
||||
device->vkDestroyDescriptorSetLayout(device->handle, pipelineContext->textureDescriptorSetLayout, NULL);
|
||||
device->vkDestroyDescriptorSetLayout(device->handle, pipelineContext->samplerDescriptorSetLayout, NULL);
|
||||
device->vkDestroyPipelineLayout(device->handle, pipelineContext->maskFillPipelineLayout, NULL);
|
||||
device->vkDestroyDescriptorSetLayout(device->handle, pipelineContext->maskFillDescriptorSetLayout, NULL);
|
||||
|
||||
|
||||
@@ -72,10 +72,13 @@ struct VKPipelineContext {
|
||||
VKDevice* device;
|
||||
VkPipelineLayout colorPipelineLayout;
|
||||
VkDescriptorSetLayout textureDescriptorSetLayout;
|
||||
VkDescriptorSetLayout samplerDescriptorSetLayout;
|
||||
VkPipelineLayout texturePipelineLayout;
|
||||
VkDescriptorSetLayout maskFillDescriptorSetLayout;
|
||||
VkPipelineLayout maskFillPipelineLayout;
|
||||
|
||||
VkDescriptorPool samplerDescriptorPool;
|
||||
VkDescriptorSet linearRepeatSamplerDescriptorSet;
|
||||
VkSampler linearRepeatSampler;
|
||||
|
||||
struct VKShaders* shaders;
|
||||
|
||||
@@ -99,6 +99,7 @@ struct VKRenderer {
|
||||
POOL(VkFramebuffer, framebufferDestructionQueue);
|
||||
ARRAY(VKMemory) bufferMemoryPages;
|
||||
ARRAY(VkDescriptorPool) descriptorPools;
|
||||
ARRAY(VkDescriptorPool) imageDescriptorPools;
|
||||
|
||||
/**
|
||||
* Last known timestamp reached by GPU execution. Resources with equal or less timestamp may be safely reused.
|
||||
@@ -253,6 +254,48 @@ static VKTexelBuffer VKRenderer_GetMaskFillBuffer(VKRenderer* renderer) {
|
||||
return texelBuffers[0];
|
||||
}
|
||||
|
||||
#define IMAGE_DESCRIPTOR_POOL_SIZE 64
|
||||
static VkDescriptorSet VKRenderer_AllocateImageDescriptorSet(VKRenderer* renderer, VkDescriptorPool descriptorPool) {
|
||||
VKDevice* device = renderer->device;
|
||||
VkDescriptorSetAllocateInfo allocateInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
|
||||
.descriptorPool = descriptorPool,
|
||||
.descriptorSetCount = 1,
|
||||
.pSetLayouts = &renderer->pipelineContext->textureDescriptorSetLayout
|
||||
};
|
||||
VkDescriptorSet set;
|
||||
VkResult result = device->vkAllocateDescriptorSets(device->handle, &allocateInfo, &set);
|
||||
if (result == VK_SUCCESS) return set;
|
||||
if (result != VK_ERROR_OUT_OF_POOL_MEMORY && result != VK_ERROR_FRAGMENTED_POOL) {
|
||||
VK_IF_ERROR(result) VK_UNHANDLED_ERROR();
|
||||
}
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
void VKRenderer_CreateImageDescriptorSet(VKRenderer* renderer, VkDescriptorPool* descriptorPool, VkDescriptorSet* set) {
|
||||
VKDevice* device = renderer->device;
|
||||
for (int i = ARRAY_SIZE(renderer->imageDescriptorPools) - 1; i >= 0; i--) {
|
||||
*set = VKRenderer_AllocateImageDescriptorSet(renderer, renderer->imageDescriptorPools[i]);
|
||||
if (*set != VK_NULL_HANDLE) {
|
||||
*descriptorPool = renderer->imageDescriptorPools[i];
|
||||
return;
|
||||
}
|
||||
}
|
||||
VkDescriptorPoolSize poolSize = {
|
||||
.type = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE,
|
||||
.descriptorCount = IMAGE_DESCRIPTOR_POOL_SIZE
|
||||
};
|
||||
VkDescriptorPoolCreateInfo poolInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
|
||||
.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT,
|
||||
.poolSizeCount = 1,
|
||||
.pPoolSizes = &poolSize,
|
||||
.maxSets = IMAGE_DESCRIPTOR_POOL_SIZE
|
||||
};
|
||||
VK_IF_ERROR(device->vkCreateDescriptorPool(device->handle, &poolInfo, NULL, descriptorPool)) VK_UNHANDLED_ERROR();
|
||||
ARRAY_PUSH_BACK(renderer->imageDescriptorPools) = *descriptorPool;
|
||||
*set = VKRenderer_AllocateImageDescriptorSet(renderer, *descriptorPool);
|
||||
}
|
||||
|
||||
static VkSemaphore VKRenderer_AddPendingSemaphore(VKRenderer* renderer) {
|
||||
VKDevice* device = renderer->device;
|
||||
VkSemaphore semaphore = VK_NULL_HANDLE;
|
||||
@@ -374,6 +417,10 @@ void VKRenderer_Destroy(VKRenderer* renderer) {
|
||||
device->vkDestroyDescriptorPool(device->handle, renderer->descriptorPools[i], NULL);
|
||||
}
|
||||
ARRAY_FREE(renderer->descriptorPools);
|
||||
for (uint32_t i = 0; i < ARRAY_SIZE(renderer->imageDescriptorPools); i++) {
|
||||
device->vkDestroyDescriptorPool(device->handle, renderer->imageDescriptorPools[i], NULL);
|
||||
}
|
||||
ARRAY_FREE(renderer->imageDescriptorPools);
|
||||
|
||||
device->vkDestroySemaphore(device->handle, renderer->timelineSemaphore, NULL);
|
||||
device->vkDestroyCommandPool(device->handle, renderer->commandPool, NULL);
|
||||
@@ -1272,48 +1319,6 @@ void VKRenderer_TextureRender(VKImage *destImage, VKImage *srcImage,
|
||||
VkCommandBuffer cb = renderPass->commandBuffer;
|
||||
VKDevice* device = surface->device;
|
||||
|
||||
// TODO We create a new descriptor set on each command, we'll implement reusing them later.
|
||||
VkDescriptorPoolSize poolSize = {
|
||||
.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
||||
.descriptorCount = 1
|
||||
};
|
||||
VkDescriptorPoolCreateInfo descrPoolInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
|
||||
.poolSizeCount = 1,
|
||||
.pPoolSizes = &poolSize,
|
||||
.maxSets = 1
|
||||
};
|
||||
VkDescriptorPool descriptorPool;
|
||||
VK_IF_ERROR(device->vkCreateDescriptorPool(device->handle, &descrPoolInfo, NULL, &descriptorPool)) VK_UNHANDLED_ERROR();
|
||||
|
||||
VkDescriptorSetAllocateInfo descrAllocInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
|
||||
.descriptorPool = descriptorPool,
|
||||
.descriptorSetCount = 1,
|
||||
.pSetLayouts = &device->renderer->pipelineContext->textureDescriptorSetLayout
|
||||
};
|
||||
VkDescriptorSet descriptorSet;
|
||||
VK_IF_ERROR(device->vkAllocateDescriptorSets(device->handle, &descrAllocInfo, &descriptorSet)) VK_UNHANDLED_ERROR();
|
||||
|
||||
VkDescriptorImageInfo imageInfo = {
|
||||
.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
||||
.imageView = VKImage_GetView(device, srcImage, srcImage->format, 0),
|
||||
.sampler = device->renderer->pipelineContext->linearRepeatSampler
|
||||
};
|
||||
|
||||
VkWriteDescriptorSet descriptorWrites = {
|
||||
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
||||
.dstSet = descriptorSet,
|
||||
.dstBinding = 0,
|
||||
.dstArrayElement = 0,
|
||||
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
||||
.descriptorCount = 1,
|
||||
.pImageInfo = &imageInfo
|
||||
};
|
||||
|
||||
device->vkUpdateDescriptorSets(device->handle, 1, &descriptorWrites, 0, NULL);
|
||||
|
||||
|
||||
// TODO We flush all pending draws and rebind the vertex buffer with the provided one.
|
||||
// We will make it work with our unified vertex buffer later.
|
||||
VKRenderer_FlushDraw(surface);
|
||||
@@ -1321,8 +1326,10 @@ void VKRenderer_TextureRender(VKImage *destImage, VKImage *srcImage,
|
||||
VkBuffer vertexBuffers[] = {vertexBuffer};
|
||||
VkDeviceSize offsets[] = {0};
|
||||
device->vkCmdBindVertexBuffers(cb, 0, 1, vertexBuffers, offsets);
|
||||
VkDescriptorSet descriptorSets[] = { VKImage_GetDescriptorSet(device, srcImage, srcImage->format, 0),
|
||||
device->renderer->pipelineContext->linearRepeatSamplerDescriptorSet };
|
||||
device->vkCmdBindDescriptorSets(cb, VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
device->renderer->pipelineContext->texturePipelineLayout, 0, 1, &descriptorSet, 0, NULL);
|
||||
device->renderer->pipelineContext->texturePipelineLayout, 0, 2, descriptorSets, 0, NULL);
|
||||
device->vkCmdDraw(cb, vertexNum, 1, 0, 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -78,6 +78,8 @@ VkCommandBuffer VKRenderer_Record(VKRenderer* renderer);
|
||||
void VKRenderer_AddImageBarrier(VkImageMemoryBarrier* barriers, VKBarrierBatch* batch,
|
||||
VKImage* image, VkPipelineStageFlags stage, VkAccessFlags access, VkImageLayout layout);
|
||||
|
||||
void VKRenderer_CreateImageDescriptorSet(VKRenderer* renderer, VkDescriptorPool* descriptorPool, VkDescriptorSet* set);
|
||||
|
||||
void VKRenderer_Destroy(VKRenderer* renderer);
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user