载入中...
搜索中...
未找到
GraphicsShaderResources.cpp
浏览该文件的文档.
1#include <algorithm>
2#include <cstring>
3#include <set>
4#include <stdexcept>
6#include "graphics/shaders/mesh3d_vert_spv.inc"
9
10namespace eve::graphics::vulkan {
11namespace {
12struct ResourceImage {
13 vk::UniqueDeviceMemory memory;
14 vk::UniqueImage image;
15 vk::UniqueImageView view;
16 vk::UniqueSampler sampler;
17 ShaderImageInput shape;
18 std::weak_ptr<const void> contentOwner;
19};
20vk::Format imageFormat(ShaderImageFormat value) {
21 switch (value) {
22 case ShaderImageFormat::R8: return vk::Format::eR8Unorm;
23 case ShaderImageFormat::RG8: return vk::Format::eR8G8Unorm;
24 case ShaderImageFormat::R16: return vk::Format::eR16Unorm;
25 case ShaderImageFormat::RGBA8: return vk::Format::eR8G8B8A8Unorm;
26 case ShaderImageFormat::BGRA8: return vk::Format::eB8G8R8A8Unorm;
27 case ShaderImageFormat::RGBA8Srgb: return vk::Format::eR8G8B8A8Srgb;
28 case ShaderImageFormat::BGRA8Srgb: return vk::Format::eB8G8R8A8Srgb;
29 case ShaderImageFormat::BC1: return vk::Format::eBc1RgbaUnormBlock;
30 case ShaderImageFormat::BC1Srgb: return vk::Format::eBc1RgbaSrgbBlock;
31 case ShaderImageFormat::BC3: return vk::Format::eBc3UnormBlock;
32 case ShaderImageFormat::BC3Srgb: return vk::Format::eBc3SrgbBlock;
33 case ShaderImageFormat::BC7: return vk::Format::eBc7UnormBlock;
34 case ShaderImageFormat::BC7Srgb: return vk::Format::eBc7SrgbBlock;
35 case ShaderImageFormat::RGBA16Unorm: return vk::Format::eR16G16B16A16Unorm;
36 case ShaderImageFormat::RGBA16Float: return vk::Format::eR16G16B16A16Sfloat;
37 case ShaderImageFormat::RGBA32Float: return vk::Format::eR32G32B32A32Sfloat;
38 }
39 return vk::Format::eUndefined;
40}
41} // namespace
42
44 std::vector<std::shared_ptr<ResourceImage>> images;
45 std::unique_ptr<vkb::GenericBuffer> constants;
46 std::vector<ShaderImageInput> shapes; // Byte spans are cleared; only reload ABI metadata remains.
47 std::size_t constantSize = 0;
48 std::vector<std::byte> constantBytes;
49 std::unique_ptr<vkb::GenericBuffer> instances;
50 std::vector<std::byte> instanceBytes;
51 vk::UniqueDescriptorSetLayout setLayout;
52 vk::UniqueDescriptorPool pool;
53 vk::UniquePipelineLayout pipelineLayout;
54};
56bool isMeshResourceImageShared(const GpuShader& first, std::uint32_t firstSlot, const GpuShader& second,
57 std::uint32_t secondSlot) {
58 return first.resources && second.resources && firstSlot < first.resources->images.size() &&
59 secondSlot < second.resources->images.size() &&
60 first.resources->images[firstSlot] == second.resources->images[secondSlot];
61}
62
63Result<void> validateMeshResourceShaderReload(const GpuShader& shader, std::span<const uint32_t> vertex,
64 std::span<const uint32_t> fragment) {
65 if (!shader.resources) return Result<void>::success();
67 vertex, fragment,
68 ShaderResourceInputs{shader.resources->shapes, shader.resources->constantBytes,
69 shader.resources->instanceBytes});
70}
71
73 return shader.resources ? static_cast<std::uint32_t>(shader.resources->instanceBytes.size() / 64) : 0;
74}
75
76Result<void> Graphics::replaceMeshShaderResources(Shader& shader, const std::vector<uint32_t>& vertSpv,
77 const std::vector<uint32_t>& fragSpv,
79 auto found = std::find_if(ownedGpuShaders.begin(), ownedGpuShaders.end(),
80 [&](const auto& gpu) { return gpu->owner == &shader; });
81 if (!initialized || found == ownedGpuShaders.end() || !(*found)->isMesh3D || (*found)->isHair3D ||
82 shader.isXray() || swapchainPassOpen || offscreen3DPassOpen)
83 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "Expected an owned, ordinary mesh shader outside frame submission", "shader.resources"));
84 if ((inputs.images.empty() && inputs.constants.empty() && inputs.instanceMatrices.empty()) ||
85 inputs.images.size() > 16 || inputs.constants.size() > 65536 || inputs.constants.size() % 16 != 0)
88 "Expected resource inputs, at most 16 images and 64 KiB of aligned uniform bytes", "shader.resources"));
89 std::set<uint32_t> bindings;
90 std::vector<std::vector<ShaderImageRegion>> regions;
91 const auto& limits = device.physical_device.properties.limits;
92 auto instanceCount = shaderInstanceMatrixCount(inputs.instanceMatrices);
94 if (inputs.instanceMatrices.size() > limits.maxStorageBufferRange ||
95 (!inputs.instanceMatrices.empty() &&
96 (limits.maxPerStageDescriptorStorageBuffers < 2 || limits.maxDescriptorSetStorageBuffers < 2)))
97 return Result<void>::failure(Diagnostic::error(DiagnosticCode::Unsupported, "Instance matrix buffer exceeds device limits", "shader.resources"));
98 if (inputs.images.size() + 13 > limits.maxPerStageDescriptorSamplers ||
99 inputs.images.size() + 13 > limits.maxPerStageDescriptorSampledImages ||
100 inputs.constants.size() > limits.maxUniformBufferRange)
101 return Result<void>::failure(Diagnostic::error(DiagnosticCode::Unsupported, "Resource program exceeds device descriptor or uniform limits", "shader.resources"));
102 for (const auto& image : inputs.images) {
103 if (!bindings.insert(image.binding).second) return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "Duplicate resource image binding", "shader.resources"));
105 if (!layout) return Result<void>::failure(layout.status());
106 const auto dimensionLimit = image.dimension == ShaderImageDimension::Image3D ? limits.maxImageDimension3D
107 : image.dimension == ShaderImageDimension::Cube ? limits.maxImageDimensionCube
108 : limits.maxImageDimension2D;
109 if (image.width > dimensionLimit || image.height > dimensionLimit || image.depth > limits.maxImageDimension3D ||
110 image.layers > limits.maxImageArrayLayers)
111 return Result<void>::failure(Diagnostic::error(DiagnosticCode::Unsupported, "Resource image exceeds device dimensions", "shader.resources"));
112 const auto flags = device.physical_device->getFormatProperties(imageFormat(image.format)).optimalTilingFeatures;
113 const auto required = vk::FormatFeatureFlagBits::eSampledImage | vk::FormatFeatureFlagBits::eTransferDst;
114 if ((flags & required) != required ||
115 ((image.sampler.min == FilterMode::Linear || image.sampler.mag == FilterMode::Linear ||
116 image.sampler.mipmap == MipmapMode::Linear) &&
117 !(flags & vk::FormatFeatureFlagBits::eSampledImageFilterLinear)))
118 return Result<void>::failure(Diagnostic::error(DiagnosticCode::Unsupported, "Device cannot sample the requested image format/filter", "shader.resources"));
119 regions.push_back(std::move(layout).takeValue());
120 }
121 auto admitted = detail::validateResourceShaderStages(vertSpv, fragSpv, inputs);
122 if (!admitted) return admitted;
123 auto candidate = std::make_unique<GpuShader>();
124 candidate->isMesh3D = true;
125 candidate->owner = &shader;
126 candidate->resources.reset(new MeshShaderResources);
127 auto& resources = *candidate->resources;
128 std::vector<uint32_t> vertex = vertSpv;
129 std::vector<uint32_t> fragment = fragSpv;
130 if (vertex.empty()) vertex.assign(mesh3d_vert_spv, mesh3d_vert_spv + mesh3d_vert_spv_count);
131 try {
132 const auto stages = vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment;
133 std::vector<vk::DescriptorSetLayoutBinding> layout;
134 for (const auto& image : inputs.images)
135 layout.emplace_back(image.binding, vk::DescriptorType::eCombinedImageSampler, 1, stages);
136 if (!inputs.constants.empty()) layout.emplace_back(32, vk::DescriptorType::eUniformBuffer, 1, stages);
137 if (!inputs.instanceMatrices.empty())
138 layout.emplace_back(33, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eVertex);
139 resources.setLayout = device->createDescriptorSetLayoutUnique(vk::DescriptorSetLayoutCreateInfo{{}, layout});
140 std::vector<vk::DescriptorPoolSize> poolSizes;
141 if (!inputs.images.empty())
142 poolSizes.emplace_back(vk::DescriptorType::eCombinedImageSampler, uint32_t(inputs.images.size()));
143 if (!inputs.constants.empty()) poolSizes.emplace_back(vk::DescriptorType::eUniformBuffer, 1);
144 if (!inputs.instanceMatrices.empty()) poolSizes.emplace_back(vk::DescriptorType::eStorageBuffer, 1);
145 resources.pool = device->createDescriptorPoolUnique(vk::DescriptorPoolCreateInfo{{}, 1, poolSizes});
146 const auto setLayout = *resources.setLayout;
147 candidate->resourceSet =
148 device->allocateDescriptorSets(vk::DescriptorSetAllocateInfo{*resources.pool, 1, &setLayout})[0];
149 const vk::DescriptorSetLayout layouts[] = {mesh3dSetLayout, setLayout};
150 const vk::PushConstantRange push{stages, 0, Shader::kPushConstantBytes};
151 resources.pipelineLayout =
152 device->createPipelineLayoutUnique(vk::PipelineLayoutCreateInfo{{}, 2, layouts, 1, &push});
153 candidate->pipelineLayout = *resources.pipelineLayout;
154 resources.images.reserve(inputs.images.size());
155 std::vector<std::unique_ptr<vkb::GenericBuffer>> pendingStaging;
156 std::vector<std::function<void(vk::CommandBuffer)>> uploadCommands;
157 size_t pendingBytes = 0;
158 auto flushUploads = [&] {
159 if (uploadCommands.empty()) return;
160 vkb::executeImmediately(device.instance, uploadPool, device.getQueue(vkb::QueueType::graphics),
161 [&](vk::CommandBuffer command) {
162 for (auto& record : uploadCommands) record(command);
163 });
164 uploadCommands.clear();
165 pendingStaging.clear();
166 pendingBytes = 0;
167 };
168 for (size_t index = 0; index < inputs.images.size(); ++index) {
169 const auto& input = inputs.images[index];
170 std::shared_ptr<ResourceImage> output;
171 if (input.contentOwner) {
172 for (const auto& prior : ownedGpuShaders) {
173 if (!prior->resources) continue;
174 for (const auto& image : prior->resources->images) {
175 auto owner = image->contentOwner.lock();
176 const auto& shape = image->shape;
177 const auto& a = shape.sampler;
178 const auto& b = input.sampler;
179 if (owner && !owner.owner_before(input.contentOwner) &&
180 !input.contentOwner.owner_before(owner) && shape.format == input.format &&
181 shape.dimension == input.dimension && shape.width == input.width &&
182 shape.height == input.height && shape.layers == input.layers &&
183 shape.depth == input.depth && shape.mipLevels == input.mipLevels && a.min == b.min &&
184 a.mag == b.mag && a.mipmap == b.mipmap && a.repeatU == b.repeatU &&
185 a.repeatV == b.repeatV && a.repeatW == b.repeatW && a.maxAnisotropy == b.maxAnisotropy &&
186 a.lodBias == b.lodBias && a.minLod == b.minLod && a.maxLod == b.maxLod) {
187 output = image;
188 break;
189 }
190 }
191 if (output) break;
192 }
193 }
194 if (!output) {
195 output = std::make_shared<ResourceImage>();
196 output->shape = input;
197 output->shape.bytes = {};
198 output->shape.contentOwner.reset();
199 output->contentOwner = input.contentOwner;
200 vk::ImageCreateInfo info{};
201 info.imageType =
202 input.dimension == ShaderImageDimension::Image3D ? vk::ImageType::e3D : vk::ImageType::e2D;
203 info.format = imageFormat(input.format);
204 info.extent = vk::Extent3D{input.width, input.height, input.depth};
205 info.mipLevels = input.mipLevels;
206 info.arrayLayers = input.layers;
207 info.samples = vk::SampleCountFlagBits::e1;
208 info.tiling = vk::ImageTiling::eOptimal;
209 info.usage = vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferDst;
210 if (input.dimension == ShaderImageDimension::Cube)
211 info.flags = vk::ImageCreateFlagBits::eCubeCompatible;
212 output->image = device->createImageUnique(info);
213 const auto memory = device->getImageMemoryRequirements(*output->image);
214 uint32_t type = UINT32_MAX;
215 const auto& props = device.physical_device.memory_properties;
216 for (uint32_t i = 0; i < props.memoryTypeCount; ++i)
217 if ((memory.memoryTypeBits & (1u << i)) &&
218 (props.memoryTypes[i].propertyFlags & vk::MemoryPropertyFlagBits::eDeviceLocal)) {
219 type = i;
220 break;
221 }
222 if (type == UINT32_MAX) throw std::runtime_error("No device-local image memory type");
223 output->memory = device->allocateMemoryUnique(vk::MemoryAllocateInfo{memory.size, type});
224 device->bindImageMemory(*output->image, *output->memory, 0);
225 const vk::ImageSubresourceRange range{vk::ImageAspectFlagBits::eColor, 0, input.mipLevels, 0,
226 input.layers};
227 const auto viewType = input.dimension == ShaderImageDimension::Cube ? vk::ImageViewType::eCube
228 : input.dimension == ShaderImageDimension::Array2D ? vk::ImageViewType::e2DArray
229 : input.dimension == ShaderImageDimension::Image3D ? vk::ImageViewType::e3D
230 : vk::ImageViewType::e2D;
231 output->view = device->createImageViewUnique(
232 vk::ImageViewCreateInfo{{}, *output->image, viewType, info.format, {}, range});
233 // A dedicated immutable sampler; use the same normalization as ordinary textures.
234 output->sampler = vk::UniqueSampler(createVkSampler(input.sampler, input.mipLevels), device.instance);
235 size_t stagingSize = 0;
236 std::vector<vk::BufferImageCopy> copies;
237 for (const auto& region : regions[index]) {
238 const auto offset = (stagingSize + 15) & ~size_t(15);
239 stagingSize = offset + region.size;
240 copies.emplace_back(
241 offset, 0, 0,
242 vk::ImageSubresourceLayers{vk::ImageAspectFlagBits::eColor, region.mip, region.layer, 1},
243 vk::Offset3D{}, vk::Extent3D{region.width, region.height, region.depth});
244 }
245 auto staging = std::make_unique<vkb::GenericBuffer>(
246 device, vk::BufferUsageFlagBits::eTransferSrc, stagingSize,
247 vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent);
248 // Copy directly into the new, unmapped upload allocation. Only the
249 // recorded regions are read by the GPU; alignment gaps need no initialization.
250 auto* mapped = static_cast<std::byte*>(staging->map());
251 for (size_t regionIndex = 0; regionIndex < regions[index].size(); ++regionIndex) {
252 const auto& region = regions[index][regionIndex];
253 std::memcpy(mapped + copies[regionIndex].bufferOffset, input.bytes.data() + region.offset,
254 region.size);
255 }
256 staging->unmap();
257 const auto buffer = staging->buffer;
258 pendingBytes += stagingSize;
259 pendingStaging.push_back(std::move(staging));
260 uploadCommands.push_back(
261 [output, range, buffer, copies = std::move(copies)](vk::CommandBuffer command) {
262 vk::ImageMemoryBarrier barrier{{},
263 vk::AccessFlagBits::eTransferWrite,
264 vk::ImageLayout::eUndefined,
265 vk::ImageLayout::eTransferDstOptimal,
266 VK_QUEUE_FAMILY_IGNORED,
267 VK_QUEUE_FAMILY_IGNORED,
268 *output->image,
269 range};
270 command.pipelineBarrier(vk::PipelineStageFlagBits::eTopOfPipe,
271 vk::PipelineStageFlagBits::eTransfer, {}, {}, {}, barrier);
272 command.copyBufferToImage(buffer, *output->image, vk::ImageLayout::eTransferDstOptimal, copies);
273 barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
274 barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;
275 barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal;
276 barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
277 command.pipelineBarrier(
278 vk::PipelineStageFlagBits::eTransfer,
279 vk::PipelineStageFlagBits::eVertexShader | vk::PipelineStageFlagBits::eFragmentShader, {},
280 {}, {}, barrier);
281 });
282 if (pendingBytes >= 64ull * 1024 * 1024) flushUploads();
283 }
284 const vk::DescriptorImageInfo descriptor{*output->sampler, *output->view,
285 vk::ImageLayout::eShaderReadOnlyOptimal};
286 device->updateDescriptorSets(vk::WriteDescriptorSet{candidate->resourceSet, input.binding, 0, 1,
287 vk::DescriptorType::eCombinedImageSampler, &descriptor},
288 {});
289 resources.images.push_back(std::move(output));
290 resources.shapes.push_back(input);
291 resources.shapes.back().bytes = {};
292 resources.shapes.back().contentOwner.reset();
293 }
294 flushUploads();
295 if (!inputs.constants.empty()) {
296 resources.constants = std::make_unique<vkb::GenericBuffer>(
297 device, vk::BufferUsageFlagBits::eUniformBuffer, inputs.constants.size(),
298 vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent);
299 resources.constants->updateLocal(vkb::FrameSlot::gpuIdle(), inputs.constants.data(),
300 inputs.constants.size());
301 resources.constantSize = inputs.constants.size();
302 resources.constantBytes.assign(inputs.constants.begin(), inputs.constants.end());
303 const vk::DescriptorBufferInfo descriptor{resources.constants->buffer, 0, inputs.constants.size()};
304 device->updateDescriptorSets(
305 vk::WriteDescriptorSet{candidate->resourceSet, 32, 0, 1, vk::DescriptorType::eUniformBuffer, nullptr,
306 &descriptor},
307 {});
308 }
309 if (!inputs.instanceMatrices.empty()) {
310 resources.instances = std::make_unique<vkb::GenericBuffer>(
311 device, vk::BufferUsageFlagBits::eStorageBuffer, inputs.instanceMatrices.size(),
312 vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent);
313 resources.instances->updateLocal(vkb::FrameSlot::gpuIdle(), inputs.instanceMatrices.data(),
314 inputs.instanceMatrices.size());
315 resources.instanceBytes.assign(inputs.instanceMatrices.begin(), inputs.instanceMatrices.end());
316 const vk::DescriptorBufferInfo descriptor{resources.instances->buffer, 0, inputs.instanceMatrices.size()};
317 device->updateDescriptorSets(
318 vk::WriteDescriptorSet{candidate->resourceSet, 33, 0, 1, vk::DescriptorType::eStorageBuffer, nullptr,
319 &descriptor},
320 {});
321 }
322 candidate->mesh3dPipeline = createMesh3DStylePipeline(
323 vertex, fragSpv, candidate->pipelineLayout, activeScenePass(), activeSceneSamples(), shader.meshBlend,
324 shader.meshDepthWrite, shader.meshDoubleSided, shader.meshRasterState());
325 candidate->mesh3dOffscreenPipeline = createMesh3DStylePipeline(
326 vertex, fragSpv, candidate->pipelineLayout, offscreen3DRenderPass, vk::SampleCountFlagBits::e1,
327 shader.meshBlend, shader.meshDepthWrite, shader.meshDoubleSided, shader.meshRasterState());
328 candidate->mesh3dHdrOffscreenPipeline = createMesh3DStylePipeline(
329 vertex, fragSpv, candidate->pipelineLayout, hdrOffscreen3DRenderPass, vk::SampleCountFlagBits::e1,
330 shader.meshBlend, shader.meshDepthWrite, shader.meshDoubleSided, shader.meshRasterState());
332 } catch (const std::exception& error) {
333 for (auto pipeline :
334 {candidate->mesh3dPipeline, candidate->mesh3dOffscreenPipeline, candidate->mesh3dHdrOffscreenPipeline})
335 if (pipeline) device->destroyPipeline(pipeline);
336 return Result<void>::failure(Diagnostic::error(DiagnosticCode::Failed, error.what(), "shader.resources"));
337 }
338 auto& old = **found;
339 for (auto pipeline :
340 {old.mesh3dPipeline, old.mesh3dXrayPipeline, old.mesh3dOffscreenPipeline, old.mesh3dHdrOffscreenPipeline})
341 if (pipeline) device->destroyPipeline(pipeline);
342 old = std::move(*candidate);
343 shader.gpuHandle = &old;
344 shader.setSpirv(std::move(vertex), std::move(fragment));
345 lastMesh3dPipeline = nullptr;
346 return Result<void>::success();
347}
348} // namespace eve::graphics::vulkan
double value
std::string output
std::string descriptor
EvpackChunkInput input
Definition Evpack.cpp:170
std::string layout
std::uint32_t instanceCount
std::weak_ptr< const void > contentOwner
vk::UniqueSampler sampler
ShaderImageInput shape
vk::UniqueImage image
vk::UniqueDeviceMemory memory
int inputs
Definition GridGraph.cpp:23
std::int32_t second
std::int32_t first
size_t offset
Range range
bool required
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
std::vector< std::weak_ptr< DeviceBytes > > resources
Definition OnnxGpgpu.cpp:55
std::vector< std::shared_ptr< DeviceBytes > > bindings
Definition OnnxGpgpu.cpp:38
std::unique_ptr< gpgpu::GpuBuffer > buffer
Definition OnnxGpgpu.cpp:26
std::string error
Definition Package.cpp:60
Shader * shader
glm::mat4 view
bool found
uint32_t index
const AssetImportLimits & limits
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
void push(bool all)
Pushes .
Custom GPU program.
Definition Shader.h:39
static constexpr uint32_t kPushConstantBytes
Definition Shader.h:43
Result< void > replaceMeshShaderResources(Shader &shader, const std::vector< uint32_t > &vertSpv, const std::vector< uint32_t > &fragSpv, const ShaderResourceInputs &resources) override
Replace mesh shader resources.
void waitForSharedGpuResources()
Block until all in-flight GPU work (all frames) has completed.
Definition Graphics.cpp:885
Result< void > validateResourceShaderStages(std::span< const uint32_t > vertex, std::span< const uint32_t > fragment, const ShaderResourceInputs &inputs)
Validate resource shader stages.
Result< void > validateMeshResourceShaderReload(const GpuShader &shader, std::span< const uint32_t > vertex, std::span< const uint32_t > fragment)
Validate mesh resource shader reload.
bool isMeshResourceImageShared(const GpuShader &first, std::uint32_t firstSlot, const GpuShader &second, std::uint32_t secondSlot)
Query whether two live shader image slots share immutable GPU storage. @ownership Borrows both shader...
std::uint32_t meshResourceInstanceCount(const GpuShader &shader)
Immutable uploaded instance count; render-thread read, no retained borrow.
ShaderImageFormat
Explicit sampled-image storage; sRGB conversion never affects alpha.
Result< std::uint32_t > shaderInstanceMatrixCount(std::span< const std::byte > bytes)
Validate packed instance records without GPU access; returns the record count.
Result< std::vector< ShaderImageRegion > > shaderImageRegions(const ShaderImageInput &image)
Validate layout, dimensions, sampler compatibility and exact payload size.
Inputs for descriptor set 1 of a resource mesh program.
GpuShader public API.
Definition Graphics.h:322
void operator()(MeshShaderResources *resources) const noexcept
Operator .
std::vector< std::shared_ptr< ResourceImage > > images
std::unique_ptr< vkb::GenericBuffer > constants
std::unique_ptr< vkb::GenericBuffer > instances
glm::uvec4 info