18#include "zeroerr/assert.h"
29#if __has_include("graphics/shaders/reflection_probe_filter_comp_spv.inc")
30#include "graphics/shaders/reflection_probe_filter_comp_spv.inc"
31#define EVENGINE_HAS_REFLECTION_PROBE_FILTER_SPV 1
37template <
class TextureImage>
39 uint32_t
height, uint32_t
depth, uint32_t mipLevels, uint32_t layers,
40 uint32_t bytesPerPixel = 4) {
42 for (uint32_t mip = 0; mip < mipLevels; ++mip) {
43 const uint32_t mipWidth = std::max(
width >> mip, 1u);
44 const uint32_t mipHeight = std::max(
height >> mip, 1u);
45 const uint32_t mipDepth = std::max(
depth >> mip, 1u);
47 image.copy(cb, staging.buffer, mip,
layer, mipWidth, mipHeight, mipDepth, uint32_t(
offset));
48 offset += vk::DeviceSize(mipWidth) * mipHeight * mipDepth * bytesPerPixel;
51 image.setLayout(cb, vk::ImageLayout::eShaderReadOnlyOptimal);
54template <
class TextureImage>
56 TextureImage &image, uint32_t
width, uint32_t
height, uint32_t mipLevels,
57 uint32_t layers,
const std::vector<uint8_t> &
bytes, uint32_t bytesPerPixel = 4,
59 vkb::GenericBuffer staging(
60 device, vk::BufferUsageFlagBits::eTransferSrc, vk::DeviceSize(
bytes.size()),
61 vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent);
62 staging.updateLocal(vkb::FrameSlot::gpuIdle(),
bytes.data(), vk::DeviceSize(
bytes.size()));
64 vkb::executeImmediately(
device.instance, commandPool, graphicsQueue, [&](vk::CommandBuffer cb) {
65 recordTextureCopies(cb, image, staging, width, height, depth, mipLevels, layers, bytesPerPixel);
70void Graphics::writeCombinedImageDescriptor(GpuTexture *gpu) {
71 if (!gpu || !gpu->descriptorSet || !gpu->sampler)
return;
72 vk::ImageView
view = gpu->imageView();
74 vkb::UnboundSet unbound = vkb::UnboundSet::reopenAfterIdle(gpu->descriptorSet);
75 vkb::DescriptorSetUpdater updater;
76 updater.beginDescriptorSet(unbound)
77 .beginImages(0, 0, vk::DescriptorType::eCombinedImageSampler)
78 .image(vkb::SampledImage::forLaterSample(gpu->sampler,
view))
79 .beginImages(1, 0, vk::DescriptorType::eCombinedImageSampler)
80 .image(vkb::SampledImage::forLaterSample(gpu->sampler,
view))
81 .update(device.instance);
82 gpu->descriptorSet = std::move(unbound).publish();
96 ASSERT(rgba !=
nullptr);
97 if (!initialized)
throw Exception(
"newTexture: graphics not initialized");
98 if (
w <= 0 ||
h <= 0 || !rgba)
throw Exception(
"newTexture: invalid args");
101 const uint32_t mipLevels =
104 auto gpu = std::make_unique<GpuTexture>();
108 gpu->mipLevels = mipLevels;
109 gpu->samplerState =
info.sampler;
110 gpu->image = vkb::TextureImage2D(device, uint32_t(
w), uint32_t(
h), mipLevels);
112 std::vector<uint8_t>
bytes =
113 (mipLevels > 1) ? buildMipChain2D(rgba, uint32_t(
w), uint32_t(
h), mipLevels)
114 : std::vector<uint8_t>(rgba, rgba +
size_t(
w) *
size_t(
h) * 4);
116 device.getQueue(vkb::QueueType::graphics), gpu->image,
117 uint32_t(
w), uint32_t(
h), mipLevels, 1,
bytes);
119 gpu->sampler = createVkSampler(
info.sampler, mipLevels);
121 auto sets = vkb::DescriptorSetBuilder().layout(texSetLayout).build(device.instance, descriptorPool);
123 gpu->descriptorSet = vkb::BoundSet{sets[0]};
124 writeCombinedImageDescriptor(gpu.get());
125 registerBindlessTexture2D(gpu.get());
127 auto tex = std::make_unique<Texture>();
131 tex->pixelHeight =
h;
132 tex->mipmapCount = int(mipLevels);
133 tex->sampler =
info.sampler;
134 tex->gpuHandle = gpu.get();
137 ownedTextures.push_back(std::move(tex));
138 ownedGpuTextures.push_back(std::move(gpu));
143 std::span<const uint8_t> rgba) {
148 const auto maximum = device.physical_device.properties.limits.maxImageDimension2D;
151 uint64_t expected = 0;
154 expected += uint64_t(
w) *
h * 4;
156 if (
w == 1 &&
h == 1)
break;
157 w = std::max(
w / 2, 1u);
158 h = std::max(
h / 2, 1u);
160 if (levels !=
count || expected != rgba.size() || expected > UINT32_MAX)
162 std::unique_ptr<GpuTexture> gpu;
165 ownedTextures.reserve(ownedTextures.size() + 1);
166 ownedGpuTextures.reserve(ownedGpuTextures.size() + 1);
167 auto tex = std::make_unique<Texture>();
168 std::vector<uint8_t>
bytes(rgba.begin(), rgba.end());
169 gpu = std::make_unique<GpuTexture>();
170 gpu->width = int(
width);
171 gpu->height = int(
height);
172 gpu->mipLevels = levels;
174 gpu->image = vkb::TextureImage2D(device,
width,
height, levels);
177 gpu->sampler = createVkSampler(gpu->samplerState, levels);
178 auto sets = vkb::DescriptorSetBuilder().layout(texSetLayout).build(device.instance, descriptorPool);
179 gpu->descriptorSet = vkb::BoundSet{sets[0]};
180 writeCombinedImageDescriptor(gpu.get());
181 tex->width = tex->pixelWidth = int(
width);
182 tex->height = tex->pixelHeight = int(
height);
183 tex->mipmapCount = int(levels);
184 tex->sampler = gpu->samplerState;
185 tex->gpuHandle = gpu.get();
186 registerBindlessTexture2D(gpu.get());
187 auto *result = tex.get();
188 ownedTextures.push_back(std::move(tex));
189 ownedGpuTextures.push_back(std::move(gpu));
191 }
catch (
const std::exception &
error) {
193 unregisterBindlessTexture(gpu.get());
194 if (gpu->descriptorSet) device->freeDescriptorSets(descriptorPool, {gpu->descriptorSet.handle});
195 if (gpu->sampler) device->destroySampler(gpu->sampler);
202 std::span<const uint16_t> rgbaHalf) {
208 const auto &
limits = device.physical_device.properties.limits;
209 const uint64_t texels = uint64_t(
width) *
height * layers;
211 layers >
limits.maxImageArrayLayers || texels > SIZE_MAX / 4 || rgbaHalf.size() != texels * 4)
213 std::unique_ptr<GpuTexture> gpu;
215 ownedTextures.reserve(ownedTextures.size() + 1);
216 ownedGpuTextures.reserve(ownedGpuTextures.size() + 1);
217 auto tex = std::make_unique<Texture>();
218 gpu = std::make_unique<GpuTexture>();
219 vk::ImageCreateInfo imageInfo{{},
221 vk::Format::eR16G16B16A16Sfloat,
225 vk::SampleCountFlagBits::e1,
226 vk::ImageTiling::eOptimal,
227 vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferDst,
228 vk::SharingMode::eExclusive};
230 vkb::GenericImage(device, imageInfo, vk::ImageViewType::e2DArray, vk::ImageAspectFlagBits::eColor,
false);
232 gpu->width = int(
width);
233 gpu->height = int(
height);
235 std::vector<uint8_t>
bytes(rgbaHalf.size_bytes());
236 std::memcpy(
bytes.data(), rgbaHalf.data(),
bytes.size());
239 gpu->sampler = createVkSampler(gpu->samplerState, 1);
240 tex->width = tex->pixelWidth = int(
width);
241 tex->height = tex->pixelHeight = int(
height);
242 tex->layers = int(layers);
243 tex->sampler = gpu->samplerState;
244 tex->gpuHandle = gpu.get();
245 auto *result = tex.get();
246 ownedTextures.push_back(std::move(tex));
247 ownedGpuTextures.push_back(std::move(gpu));
249 }
catch (
const std::exception &
error) {
250 if (gpu && gpu->sampler) device->destroySampler(gpu->sampler);
256 std::span<const uint8_t> rgba) {
262 const auto &
limits = device.physical_device.properties.limits;
265 depth >
limits.maxImageDimension3D || texels > SIZE_MAX / 4 || rgba.size() != texels * 4)
267 std::unique_ptr<GpuTexture> gpu;
269 ownedTextures.reserve(ownedTextures.size() + 1);
270 ownedGpuTextures.reserve(ownedGpuTextures.size() + 1);
271 auto tex = std::make_unique<Texture>();
272 gpu = std::make_unique<GpuTexture>();
273 vk::ImageCreateInfo imageInfo{{},
275 vk::Format::eR8G8B8A8Unorm,
279 vk::SampleCountFlagBits::e1,
280 vk::ImageTiling::eOptimal,
281 vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferDst,
282 vk::SharingMode::eExclusive};
284 vkb::GenericImage(device, imageInfo, vk::ImageViewType::e3D, vk::ImageAspectFlagBits::eColor,
false);
286 gpu->isVolume =
true;
287 gpu->width = int(
width);
288 gpu->height = int(
height);
290 gpu->samplerState.repeatU = gpu->samplerState.repeatV = gpu->samplerState.repeatW =
true;
291 std::vector<uint8_t>
bytes(rgba.begin(), rgba.end());
294 gpu->sampler = createVkSampler(gpu->samplerState, 1);
295 tex->width = tex->pixelWidth = int(
width);
296 tex->height = tex->pixelHeight = int(
height);
297 tex->depth = int(
depth);
298 tex->sampler = gpu->samplerState;
299 tex->gpuHandle = gpu.get();
300 auto *result = tex.get();
301 ownedTextures.push_back(std::move(tex));
302 ownedGpuTextures.push_back(std::move(gpu));
304 }
catch (
const std::exception &
error) {
305 if (gpu && gpu->sampler) device->destroySampler(gpu->sampler);
318 ASSERT_GT(faceSize, 0);
319 ASSERT(rgbaFaces !=
nullptr);
320 if (!initialized)
throw Exception(
"newCubemap: graphics not initialized");
321 if (faceSize <= 0 || !rgbaFaces)
throw Exception(
"newCubemap: invalid args");
324 info.sampler.repeatU =
false;
325 info.sampler.repeatV =
false;
326 info.sampler.repeatW =
false;
327 const uint32_t mipLevels =
330 const size_t faceBytes = size_t(faceSize) * size_t(faceSize) * 4u;
331 auto gpu = std::make_unique<GpuTexture>();
332 gpu->width = faceSize;
333 gpu->height = faceSize;
335 gpu->mipLevels = mipLevels;
336 gpu->samplerState =
info.sampler;
337 gpu->cubeImage = vkb::TextureImageCube(device, device.physical_device.memory_properties,
338 uint32_t(faceSize), uint32_t(faceSize), mipLevels);
340 std::vector<uint8_t>
bytes =
341 (mipLevels > 1) ? buildMipChainCube(rgbaFaces, uint32_t(faceSize), mipLevels)
342 : std::vector<uint8_t>(rgbaFaces, rgbaFaces + faceBytes * 6u);
344 device.getQueue(vkb::QueueType::graphics), gpu->cubeImage,
345 uint32_t(faceSize), uint32_t(faceSize), mipLevels, 6,
bytes);
347 gpu->sampler = createVkSampler(
info.sampler, mipLevels);
349 registerBindlessTextureCube(gpu.get());
351 auto tex = std::make_unique<Texture>();
352 tex->width = faceSize;
353 tex->height = faceSize;
354 tex->pixelWidth = faceSize;
355 tex->pixelHeight = faceSize;
357 tex->mipmapCount = int(mipLevels);
358 tex->sampler =
info.sampler;
359 tex->gpuHandle = gpu.get();
362 ownedTextures.push_back(std::move(tex));
363 ownedGpuTextures.push_back(std::move(gpu));
368 if (!initialized || faceSize <= 0)
return nullptr;
369 const uint32_t
size =
static_cast<uint32_t
>(faceSize);
371 vk::ImageCreateInfo imageInfo{};
372 imageInfo.flags = vk::ImageCreateFlagBits::eCubeCompatible;
373 imageInfo.imageType = vk::ImageType::e2D;
374 imageInfo.format = vk::Format::eR16G16B16A16Sfloat;
375 imageInfo.extent = vk::Extent3D{
size,
size, 1};
376 imageInfo.mipLevels = mipLevels;
377 imageInfo.arrayLayers = 6;
378 imageInfo.samples = vk::SampleCountFlagBits::e1;
379 imageInfo.tiling = vk::ImageTiling::eOptimal;
380 imageInfo.usage = vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferDst |
381 vk::ImageUsageFlagBits::eTransferSrc |
382 vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eStorage;
383 imageInfo.sharingMode = vk::SharingMode::eExclusive;
385 auto gpu = std::make_unique<GpuTexture>();
386 gpu->rawCubeImage = device->createImageUnique(imageInfo);
387 const vk::MemoryRequirements requirements =
388 device->getImageMemoryRequirements(*gpu->rawCubeImage);
389 uint32_t memoryType = UINT32_MAX;
390 const auto &memoryProperties = device.physical_device.memory_properties;
391 for (uint32_t
index = 0;
index < memoryProperties.memoryTypeCount; ++
index) {
392 if ((requirements.memoryTypeBits & (1u <<
index)) != 0
u &&
393 (memoryProperties.memoryTypes[
index].propertyFlags &
394 vk::MemoryPropertyFlagBits::eDeviceLocal) != vk::MemoryPropertyFlags{}) {
399 if (memoryType == UINT32_MAX)
return nullptr;
400 gpu->rawCubeMemory = device->allocateMemoryUnique(
401 vk::MemoryAllocateInfo{requirements.size, memoryType});
402 device->bindImageMemory(*gpu->rawCubeImage, *gpu->rawCubeMemory, 0);
404 vk::ImageViewCreateInfo viewInfo{};
405 viewInfo.image = *gpu->rawCubeImage;
406 viewInfo.viewType = vk::ImageViewType::eCube;
407 viewInfo.format = imageInfo.format;
408 viewInfo.subresourceRange =
409 vk::ImageSubresourceRange{vk::ImageAspectFlagBits::eColor, 0, mipLevels, 0, 6};
410 gpu->rawCubeView = device->createImageViewUnique(viewInfo);
411 gpu->width = faceSize;
412 gpu->height = faceSize;
415 gpu->mipLevels = mipLevels;
417 gpu->sampler = createVkSampler(gpu->samplerState, mipLevels);
419 vkb::executeImmediately(device.instance, uploadPool,
420 device.getQueue(vkb::QueueType::graphics),
421 [&](vk::CommandBuffer command) {
422 vk::ImageMemoryBarrier barrier{};
423 barrier.srcAccessMask = {};
424 barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;
425 barrier.oldLayout = vk::ImageLayout::eUndefined;
426 barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
427 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
428 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
429 barrier.image = *gpu->rawCubeImage;
430 barrier.subresourceRange = viewInfo.subresourceRange;
431 command.pipelineBarrier(vk::PipelineStageFlagBits::eTopOfPipe,
432 vk::PipelineStageFlagBits::eFragmentShader,
433 {}, 0,
nullptr, 0,
nullptr, 1, &barrier);
435 registerBindlessTextureCube(gpu.get());
437 auto texture = std::make_unique<Texture>();
438 texture->width = faceSize;
439 texture->height = faceSize;
440 texture->pixelWidth = faceSize;
441 texture->pixelHeight = faceSize;
443 texture->mipmapCount = int(mipLevels);
444 texture->sampler = gpu->samplerState;
445 texture->gpuHandle = gpu.get();
447 ownedTextures.push_back(std::move(texture));
448 ownedGpuTextures.push_back(std::move(gpu));
454 if (!canvas || !canvas->isHDR() || !cubemap || !cubemap->
gpuHandle || face < 0 || face >= 6)
458 canvas->getWidth() !=
target->width || canvas->getHeight() !=
target->height)
460 ensureOffscreen3DResources();
461 lastOffscreen3DGpuDurationMs = 0.f;
462 vkb::executeImmediately(
device.instance, uploadPool,
463 device.getQueue(vkb::QueueType::graphics),
464 [&](vk::CommandBuffer command) {
465 if (offscreen3DTimestampQueryPool) {
466 command.resetQueryPool(offscreen3DTimestampQueryPool, 0, 2);
467 command.writeTimestamp(vk::PipelineStageFlagBits::eTopOfPipe,
468 offscreen3DTimestampQueryPool, 0);
470 canvas->colorImage().setLayout(command,
471 vk::ImageLayout::eTransferSrcOptimal);
472 vk::ImageMemoryBarrier toCopy{};
473 toCopy.srcAccessMask = vk::AccessFlagBits::eShaderRead;
474 toCopy.dstAccessMask = vk::AccessFlagBits::eTransferWrite;
475 toCopy.oldLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
476 toCopy.newLayout = vk::ImageLayout::eTransferDstOptimal;
477 toCopy.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
478 toCopy.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
479 toCopy.image = *
target->rawCubeImage;
480 toCopy.subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1,
482 command.pipelineBarrier(vk::PipelineStageFlagBits::eFragmentShader,
483 vk::PipelineStageFlagBits::eTransfer, {}, 0,
484 nullptr, 0,
nullptr, 1, &toCopy);
485 vk::ImageCopy copy{};
486 copy.srcSubresource = {vk::ImageAspectFlagBits::eColor, 0, 0, 1};
487 copy.dstSubresource = {vk::ImageAspectFlagBits::eColor, 0,
489 copy.extent = vk::Extent3D{uint32_t(
target->width),
490 uint32_t(
target->height), 1};
491 command.copyImage(canvas->colorImage().image(),
492 vk::ImageLayout::eTransferSrcOptimal,
494 vk::ImageLayout::eTransferDstOptimal, 1, ©);
495 std::swap(toCopy.srcAccessMask, toCopy.dstAccessMask);
496 std::swap(toCopy.oldLayout, toCopy.newLayout);
497 command.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
498 vk::PipelineStageFlagBits::eFragmentShader,
499 {}, 0,
nullptr, 0,
nullptr, 1, &toCopy);
500 canvas->colorImage().setLayout(
501 command, vk::ImageLayout::eShaderReadOnlyOptimal);
502 if (offscreen3DTimestampQueryPool)
503 command.writeTimestamp(
504 vk::PipelineStageFlagBits::eBottomOfPipe,
505 offscreen3DTimestampQueryPool, 1);
507 if (offscreen3DTimestampQueryPool && offscreen3DTimestampPeriodNs > 0.f) {
508 std::array<uint64_t, 2> ticks{};
509 const vk::Result result =
device->getQueryPoolResults(
510 offscreen3DTimestampQueryPool, 0, uint32_t(ticks.size()),
sizeof(ticks), ticks.data(),
511 sizeof(uint64_t), vk::QueryResultFlagBits::e64 | vk::QueryResultFlagBits::eWait);
512 if (result == vk::Result::eSuccess && ticks[1] >= ticks[0])
513 lastOffscreen3DGpuDurationMs =
514 float(
double(ticks[1] - ticks[0]) *
double(offscreen3DTimestampPeriodNs) * 1.0e-6);
521 if (!sources || faceCount < 1 || faceCount > 6 || !cubemap || !cubemap->
gpuHandle)
526 std::array<OffscreenCanvas *, 6> canvases{};
527 for (
int face = 0; face < faceCount; ++face) {
529 if (!canvas || !canvas->isHDR() || canvas->getWidth() !=
target->width ||
530 canvas->getHeight() !=
target->height)
532 canvases[
static_cast<size_t>(face)] = canvas;
535 ensureOffscreen3DResources();
536 lastOffscreen3DGpuDurationMs = 0.f;
537 vkb::executeImmediately(
device.instance, uploadPool,
538 device.getQueue(vkb::QueueType::graphics),
539 [&](vk::CommandBuffer command) {
540 if (offscreen3DTimestampQueryPool) {
541 command.resetQueryPool(offscreen3DTimestampQueryPool, 0, 2);
542 command.writeTimestamp(vk::PipelineStageFlagBits::eTopOfPipe,
543 offscreen3DTimestampQueryPool, 0);
545 for (
int face = 0; face < faceCount; ++face) {
546 auto *canvas = canvases[
static_cast<size_t>(face)];
547 canvas->colorImage().setLayout(
548 command, vk::ImageLayout::eTransferSrcOptimal);
550 vk::ImageMemoryBarrier toCopy{};
551 toCopy.srcAccessMask = vk::AccessFlagBits::eShaderRead;
552 toCopy.dstAccessMask = vk::AccessFlagBits::eTransferWrite;
553 toCopy.oldLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
554 toCopy.newLayout = vk::ImageLayout::eTransferDstOptimal;
555 toCopy.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
556 toCopy.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
557 toCopy.image = *
target->rawCubeImage;
558 toCopy.subresourceRange =
559 vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor,
560 0, 1, uint32_t(face), 1);
561 command.pipelineBarrier(
562 vk::PipelineStageFlagBits::eFragmentShader,
563 vk::PipelineStageFlagBits::eTransfer, {}, 0,
nullptr, 0,
564 nullptr, 1, &toCopy);
566 vk::ImageCopy copy{};
567 copy.srcSubresource =
568 vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor,
570 copy.dstSubresource =
571 vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor,
572 0, uint32_t(face), 1);
573 copy.extent = vk::Extent3D(uint32_t(canvas->getWidth()),
574 uint32_t(canvas->getHeight()), 1);
575 command.copyImage(canvas->colorImage().image(),
576 vk::ImageLayout::eTransferSrcOptimal,
578 vk::ImageLayout::eTransferDstOptimal, 1,
581 std::swap(toCopy.srcAccessMask, toCopy.dstAccessMask);
582 std::swap(toCopy.oldLayout, toCopy.newLayout);
583 command.pipelineBarrier(
584 vk::PipelineStageFlagBits::eTransfer,
585 vk::PipelineStageFlagBits::eFragmentShader, {}, 0,
nullptr,
586 0,
nullptr, 1, &toCopy);
587 canvas->colorImage().setLayout(
588 command, vk::ImageLayout::eShaderReadOnlyOptimal);
590 if (offscreen3DTimestampQueryPool)
591 command.writeTimestamp(
592 vk::PipelineStageFlagBits::eBottomOfPipe,
593 offscreen3DTimestampQueryPool, 1);
595 if (offscreen3DTimestampQueryPool && offscreen3DTimestampPeriodNs > 0.f) {
596 std::array<uint64_t, 2> ticks{};
597 const vk::Result result =
device->getQueryPoolResults(
598 offscreen3DTimestampQueryPool, 0, uint32_t(ticks.size()),
sizeof(ticks), ticks.data(),
599 sizeof(uint64_t), vk::QueryResultFlagBits::e64 | vk::QueryResultFlagBits::eWait);
600 if (result == vk::Result::eSuccess && ticks[1] >= ticks[0])
601 lastOffscreen3DGpuDurationMs =
602 float(
double(ticks[1] - ticks[0]) *
double(offscreen3DTimestampPeriodNs) * 1.0e-6);
608#ifndef EVENGINE_HAS_REFLECTION_PROBE_FILTER_SPV
613 if (!cubemap || !cubemap->
gpuHandle)
return false;
617 sampleCount = std::clamp(sampleCount, 8, 512);
618 ensureOffscreen3DResources();
619 lastOffscreen3DGpuDurationMs = 0.f;
621 if (!reflectionProbeFilterPass.pipeline()) {
622 std::array<vk::DescriptorSetLayoutBinding, 2>
bindings{
623 vk::DescriptorSetLayoutBinding{0, vk::DescriptorType::eCombinedImageSampler, 1,
624 vk::ShaderStageFlagBits::eCompute},
625 vk::DescriptorSetLayoutBinding{1, vk::DescriptorType::eStorageImage, 1,
626 vk::ShaderStageFlagBits::eCompute},
628 reflectionProbeFilterSetLayout =
device->createDescriptorSetLayoutUnique(
629 vk::DescriptorSetLayoutCreateInfo{{}, uint32_t(
bindings.size()),
bindings.data()});
630 vk::DescriptorSetLayout
layout = *reflectionProbeFilterSetLayout;
631 vk::PushConstantRange
push{vk::ShaderStageFlagBits::eCompute, 0, 16};
632 reflectionProbeFilterPipelineLayout =
device->createPipelineLayoutUnique(
633 vk::PipelineLayoutCreateInfo{{}, 1, &
layout, 1, &
push});
634 const std::vector<uint32_t> spv(reflection_probe_filter_comp_spv,
635 reflection_probe_filter_comp_spv +
636 reflection_probe_filter_comp_spv_count);
637 if (!reflectionProbeFilterPass.create(
device, *reflectionProbeFilterPipelineLayout, spv))
641 vk::ImageViewCreateInfo sourceInfo{};
642 sourceInfo.image = *
target->rawCubeImage;
643 sourceInfo.viewType = vk::ImageViewType::eCube;
644 sourceInfo.format = vk::Format::eR16G16B16A16Sfloat;
645 sourceInfo.subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 6};
646 vk::UniqueImageView sourceView =
device->createImageViewUnique(sourceInfo);
647 std::vector<vk::UniqueImageView> targetViews;
648 targetViews.reserve(
target->mipLevels - 1u);
649 for (uint32_t mip = 1; mip <
target->mipLevels; ++mip) {
650 vk::ImageViewCreateInfo
info{};
652 info.viewType = vk::ImageViewType::e2DArray;
653 info.format = sourceInfo.format;
654 info.subresourceRange = {vk::ImageAspectFlagBits::eColor, mip, 1, 0, 6};
655 targetViews.push_back(
device->createImageViewUnique(
info));
658 std::array<vk::DescriptorPoolSize, 2> poolSizes{
659 vk::DescriptorPoolSize{vk::DescriptorType::eCombinedImageSampler,
661 vk::DescriptorPoolSize{vk::DescriptorType::eStorageImage,
target->mipLevels - 1u},
663 vk::UniqueDescriptorPool pool =
device->createDescriptorPoolUnique(vk::DescriptorPoolCreateInfo{
664 {},
target->mipLevels - 1u, uint32_t(poolSizes.size()), poolSizes.data()});
665 std::vector<vk::DescriptorSetLayout> layouts(
target->mipLevels - 1u,
666 *reflectionProbeFilterSetLayout);
667 std::vector<vk::DescriptorSet> sets =
device->allocateDescriptorSets(
668 vk::DescriptorSetAllocateInfo{*pool, uint32_t(layouts.size()), layouts.data()});
670 vk::DescriptorImageInfo sourceImage{
target->sampler, *sourceView,
671 vk::ImageLayout::eShaderReadOnlyOptimal};
672 vk::DescriptorImageInfo destinationImage{{}, *targetViews[
index],
673 vk::ImageLayout::eGeneral};
674 std::array<vk::WriteDescriptorSet, 2> writes{
675 vk::WriteDescriptorSet{sets[
index], 0, 0, 1,
676 vk::DescriptorType::eCombinedImageSampler, &sourceImage},
677 vk::WriteDescriptorSet{sets[
index], 1, 0, 1,
678 vk::DescriptorType::eStorageImage, &destinationImage},
680 device->updateDescriptorSets(uint32_t(writes.size()), writes.data(), 0,
nullptr);
685 uint32_t sampleCount;
686 uint32_t diffuseMode;
689 vkb::executeImmediately(
device.instance, uploadPool,
690 device.getQueue(vkb::QueueType::graphics),
691 [&](vk::CommandBuffer command) {
692 if (offscreen3DTimestampQueryPool) {
693 command.resetQueryPool(offscreen3DTimestampQueryPool, 0, 2);
694 command.writeTimestamp(vk::PipelineStageFlagBits::eTopOfPipe,
695 offscreen3DTimestampQueryPool, 0);
697 for (uint32_t mip = 1; mip <
target->mipLevels; ++mip) {
698 vk::ImageMemoryBarrier toWrite{};
699 toWrite.srcAccessMask = vk::AccessFlagBits::eShaderRead;
700 toWrite.dstAccessMask = vk::AccessFlagBits::eShaderWrite;
701 toWrite.oldLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
702 toWrite.newLayout = vk::ImageLayout::eGeneral;
703 toWrite.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
704 toWrite.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
705 toWrite.image = *
target->rawCubeImage;
706 toWrite.subresourceRange = {vk::ImageAspectFlagBits::eColor, mip,
708 command.pipelineBarrier(vk::PipelineStageFlagBits::eFragmentShader,
709 vk::PipelineStageFlagBits::eComputeShader,
710 {}, 0,
nullptr, 0,
nullptr, 1, &toWrite);
711 const uint32_t
size = std::max(uint32_t(
target->width) >> mip, 1u);
712 const FilterPush
push{
713 float(mip) / float(
target->mipLevels - 1u),
714 uint32_t(sampleCount),
716 command.bindDescriptorSets(
717 vk::PipelineBindPoint::eCompute,
718 *reflectionProbeFilterPipelineLayout, 0, 1,
719 &sets[
static_cast<size_t>(mip - 1u)], 0,
nullptr);
720 command.pushConstants(*reflectionProbeFilterPipelineLayout,
721 vk::ShaderStageFlagBits::eCompute, 0,
723 reflectionProbeFilterPass.record(command, (
size + 7u) / 8u,
724 (
size + 7u) / 8u, 6);
725 std::swap(toWrite.srcAccessMask, toWrite.dstAccessMask);
726 std::swap(toWrite.oldLayout, toWrite.newLayout);
727 command.pipelineBarrier(vk::PipelineStageFlagBits::eComputeShader,
728 vk::PipelineStageFlagBits::eFragmentShader,
729 {}, 0,
nullptr, 0,
nullptr, 1, &toWrite);
731 if (offscreen3DTimestampQueryPool)
732 command.writeTimestamp(
733 vk::PipelineStageFlagBits::eBottomOfPipe,
734 offscreen3DTimestampQueryPool, 1);
736 if (offscreen3DTimestampQueryPool && offscreen3DTimestampPeriodNs > 0.f) {
737 std::array<uint64_t, 2> ticks{};
738 const vk::Result result =
device->getQueryPoolResults(
739 offscreen3DTimestampQueryPool, 0, uint32_t(ticks.size()),
sizeof(ticks), ticks.data(),
740 sizeof(uint64_t), vk::QueryResultFlagBits::e64 | vk::QueryResultFlagBits::eWait);
741 if (result == vk::Result::eSuccess && ticks[1] >= ticks[0])
742 lastOffscreen3DGpuDurationMs =
743 float(
double(ticks[1] - ticks[0]) *
double(offscreen3DTimestampPeriodNs) * 1.0e-6);
750 ASSERT(data !=
nullptr);
751 if (!data)
throw Exception(
"newTexture: null ImageData");
752 if (data->getFormat() !=
"RGBA8")
753 throw Exception(
"newTexture: only RGBA8 ImageData supported for now");
754 return newTexture(data->getWidth(), data->getHeight(),
755 static_cast<const uint8_t *
>(data->getData()));
759 ASSERT(data !=
nullptr);
760 if (!data)
throw Exception(
"newTexture: null ImageData");
761 if (data->getFormat() !=
"RGBA8")
762 throw Exception(
"newTexture: only RGBA8 ImageData supported for now");
763 return newTexture(data->getWidth(), data->getHeight(),
764 static_cast<const uint8_t *
>(data->getData()),
info);
768 if (!data || contentKey.empty() || data->getFormat() !=
"RGBA8")
770 "RGBA8 image and nonempty content key are required", {},
771 {},
"graphics.texture.shared"));
772 if (
auto found = sharedTexturesByContent.find(contentKey);
found != sharedTexturesByContent.end())
776 if (!texture)
throw Exception(
"shared texture upload produced no texture");
777 sharedTexturesByContent.emplace(contentKey, texture);
779 }
catch (
const std::exception &
error) {
788 if (!texture || !texture->
gpuHandle || !initialized)
return;
789 for (
auto &
owned : ownedGpuTextures) {
792 waitForSharedGpuResources();
797 if (!
owned->isCube) writeCombinedImageDescriptor(
owned.get());
798 invalidateTextureBindings();
804 if (!texture)
return false;
807 auto texIt = std::find_if(ownedTextures.begin(), ownedTextures.end(),
808 [&](
const std::unique_ptr<Texture> &
t) {
809 return t.get() == texture;
811 if (texIt == ownedTextures.end())
return false;
812 for (
auto it = texturesByPath.begin(); it != texturesByPath.end();) {
813 if (it->second == texture)
814 it = texturesByPath.erase(it);
818 for (
auto it = sharedTexturesByContent.begin(); it != sharedTexturesByContent.end();) {
819 if (it->second == texture)
820 it = sharedTexturesByContent.erase(it);
824 (void)texIt->release();
825 ownedTextures.erase(texIt);
829 if (texture == whiteTexture || texture == flatNormalTexture || texture == flatNormalTexture3D ||
830 texture == defaultEnvCubemap || texture == defaultExtrasArray || texture == defaultColorsArray ||
831 texture == defaultVertexArray || texture == defaultMotionArray || texture == defaultVegetationFadeNoise)
835 auto gpuIt = std::find_if(ownedGpuTextures.begin(), ownedGpuTextures.end(),
836 [&](
const std::unique_ptr<GpuTexture> &
g) {
837 return g.get() == gpu;
839 if (gpuIt == ownedGpuTextures.end())
return false;
841 auto texIt = std::find_if(ownedTextures.begin(), ownedTextures.end(),
842 [&](
const std::unique_ptr<Texture> &
t) {
843 return t.get() == texture;
845 if (texIt == ownedTextures.end())
return false;
848 for (
auto it = texturesByPath.begin(); it != texturesByPath.end();) {
849 if (it->second == texture)
850 it = texturesByPath.erase(it);
854 for (
auto it = sharedTexturesByContent.begin(); it != sharedTexturesByContent.end();) {
855 if (it->second == texture)
856 it = sharedTexturesByContent.erase(it);
862 waitForSharedGpuResources();
863 unregisterBindlessTexture(gpu);
864 if ((*gpuIt)->sampler)
device->destroySampler((*gpuIt)->sampler);
866 ownedGpuTextures.erase(gpuIt);
868 (void)texIt->release();
869 ownedTextures.erase(texIt);
874 if (!tex || !data)
return false;
875 if (data->getFormat() !=
"RGBA8")
return false;
876 return replaceTexturePixelsRGBA(tex, data->getWidth(), data->getHeight(),
877 static_cast<const uint8_t *
>(data->getData()));
881 if (!tex ||
width <= 0 ||
height <= 0 || !rgba)
return false;
887 const auto bytes = std::span<const uint8_t>(rgba,
size_t(
width) *
size_t(
height) * 4u);
890 return replaceTexturePixelsRGBA(tex,
width,
height, rgba);
895 constexpr const char*
context =
"graphics.updateTextureFromResidentRgba8";
899 "Resident upload requires a matching owned single-mip texture",
context));
903 (
source.offsetBytes & 3u) != 0)
907 "Resident RGBA8 buffer is incompatible with the Vulkan texture",
context));
911 std::find_if(ownedGpuTextures.begin(), ownedGpuTextures.end(),
912 [gpu](
const std::unique_ptr<GpuTexture>& candidate) { return candidate.get() == gpu; });
913 if (!initialized || !gpu ||
owned == ownedGpuTextures.end() || gpu->isCube)
915 "Texture is not an owned Vulkan 2D texture",
context));
917 VkBuffer rawBuffer = VK_NULL_HANDLE;
918 static_assert(
sizeof(rawBuffer) <=
sizeof(
source.nativeHandle));
919 std::memcpy(&rawBuffer, &
source.nativeHandle,
sizeof(rawBuffer));
920 const vk::Buffer
buffer(rawBuffer);
922 waitForSharedGpuResources();
923 vkb::executeImmediately(
924 device.instance, uploadPool,
device.getQueue(vkb::QueueType::graphics), [&](vk::CommandBuffer cb) {
925 gpu->image.setLayout(cb, vk::ImageLayout::eTransferDstOptimal);
926 vk::BufferImageCopy copy{};
927 copy.bufferOffset =
source.offsetBytes;
928 copy.imageSubresource = {vk::ImageAspectFlagBits::eColor, 0, 0, 1};
929 copy.imageExtent = vk::Extent3D{uint32_t(
width), uint32_t(
height), 1};
930 cb.copyBufferToImage(
buffer, gpu->image.image(), vk::ImageLayout::eTransferDstOptimal, copy);
932 vk::ImageMemoryBarrier barrier{};
933 barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
934 barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;
935 barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal;
936 barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
937 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
938 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
939 barrier.image = gpu->image.image();
940 barrier.subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1};
941 cb.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
942 vk::PipelineStageFlagBits::eVertexShader | vk::PipelineStageFlagBits::eFragmentShader |
943 vk::PipelineStageFlagBits::eComputeShader,
944 {}, 0,
nullptr, 0,
nullptr, 1, &barrier);
945 gpu->image.setCurrentLayout(vk::ImageLayout::eShaderReadOnlyOptimal);
952 std::span<const std::uint8_t> rgba,
953 std::size_t bytesPerRow) {
959 Texture *tex, std::span<const TextureRegionUpload> regions) {
963 "graphics.updateTextureRegions.texture"));
967 "graphics.updateTextureRegions.mipmaps"));
970 const auto owned = std::find_if(ownedGpuTextures.begin(), ownedGpuTextures.end(),
971 [gpu](
const std::unique_ptr<GpuTexture> &candidate) {
972 return candidate.get() == gpu;
974 if (!gpu ||
owned == ownedGpuTextures.end() || gpu->isCube)
977 "graphics.updateTextureRegions.texture"));
980 std::vector<std::size_t> packedOffsets;
981 packedOffsets.reserve(regions.size());
982 std::size_t packedSize = 0;
984 if (region.x < 0 || region.y < 0 || region.width <= 0 || region.height <= 0 ||
985 region.x > tex->
width - region.width || region.y > tex->
height - region.height)
988 "graphics.updateTextureRegions.region"));
989 const std::size_t packedRow = std::size_t(region.width) * 4U;
990 const std::size_t stride = region.bytesPerRow == 0 ? packedRow : region.bytesPerRow;
991 const std::size_t requiredBytes = stride * std::size_t(region.height - 1) + packedRow;
992 if (stride < packedRow || region.rgba.size() < requiredBytes)
995 "source bytes do not cover the texture region",
996 "graphics.updateTextureRegions.bytes"));
997 packedOffsets.push_back(packedSize);
998 packedSize += packedRow * std::size_t(region.height);
1001 std::vector<std::uint8_t> packed(packedSize);
1004 const std::size_t packedRow = std::size_t(region.
width) * 4U;
1006 for (
int row = 0; row < region.
height; ++row)
1007 std::copy_n(region.
rgba.data() + std::size_t(row) * stride, packedRow,
1008 packed.data() + packedOffsets[
index] + std::size_t(row) * packedRow);
1010 vkb::GenericBuffer staging(
1011 device, vk::BufferUsageFlagBits::eTransferSrc, vk::DeviceSize(packed.size()),
1012 vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent);
1013 staging.updateLocal(vkb::FrameSlot::gpuIdle(), packed.data(), vk::DeviceSize(packed.size()));
1015 waitForSharedGpuResources();
1016 vkb::executeImmediately(
device.instance, uploadPool,
1017 device.getQueue(vkb::QueueType::graphics),
1018 [&](vk::CommandBuffer cb) {
1019 gpu->image.setLayout(cb, vk::ImageLayout::eTransferDstOptimal);
1020 std::vector<vk::BufferImageCopy> copies;
1021 copies.reserve(regions.size());
1022 for (std::size_t index = 0; index < regions.size(); ++index) {
1023 const TextureRegionUpload &source = regions[index];
1024 vk::BufferImageCopy copy{};
1025 copy.bufferOffset = packedOffsets[index];
1026 copy.imageSubresource = {vk::ImageAspectFlagBits::eColor, 0, 0, 1};
1027 copy.imageOffset = vk::Offset3D{source.x, source.y, 0};
1028 copy.imageExtent = vk::Extent3D{std::uint32_t(source.width),
1029 std::uint32_t(source.height), 1};
1030 copies.push_back(copy);
1032 cb.copyBufferToImage(staging.buffer, gpu->image.image(),
1033 vk::ImageLayout::eTransferDstOptimal, copies);
1035 vk::ImageMemoryBarrier barrier{};
1036 barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
1037 barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;
1038 barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal;
1039 barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
1040 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1041 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1042 barrier.image = gpu->image.image();
1043 barrier.subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1};
1044 cb.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
1045 vk::PipelineStageFlagBits::eVertexShader |
1046 vk::PipelineStageFlagBits::eFragmentShader |
1047 vk::PipelineStageFlagBits::eComputeShader,
1048 {}, 0,
nullptr, 0,
nullptr, 1, &barrier);
1049 gpu->image.setCurrentLayout(vk::ImageLayout::eShaderReadOnlyOptimal);
1055bool Graphics::replaceTexturePixelsRGBA(
Texture *tex,
int w,
int h,
const uint8_t *rgba) {
1056 if (!tex || !rgba ||
w <= 0 ||
h <= 0 || !initialized)
return false;
1062 const uint32_t mipLevels =
1065 auto gpu = std::make_unique<GpuTexture>();
1068 gpu->isCube =
false;
1069 gpu->mipLevels = mipLevels;
1070 gpu->samplerState =
info.sampler;
1071 gpu->image = vkb::TextureImage2D(
device, uint32_t(
w), uint32_t(
h), mipLevels);
1072 std::vector<uint8_t>
bytes =
1073 (mipLevels > 1) ? buildMipChain2D(rgba, uint32_t(
w), uint32_t(
h), mipLevels)
1074 : std::vector<uint8_t>(rgba, rgba +
size_t(
w) *
size_t(
h) * 4);
1076 device.getQueue(vkb::QueueType::graphics), gpu->image,
1077 uint32_t(
w), uint32_t(
h), mipLevels, 1,
bytes);
1079 gpu->sampler = createVkSampler(
info.sampler, mipLevels);
1081 auto sets = vkb::DescriptorSetBuilder().layout(texSetLayout).build(
device.instance, descriptorPool);
1083 gpu->descriptorSet = vkb::BoundSet{sets[0]};
1084 writeCombinedImageDescriptor(gpu.get());
1085 registerBindlessTexture2D(gpu.get());
1088 for (
auto &
owned : ownedGpuTextures) {
1089 if (
owned.get() != oldHandle)
continue;
1092 waitForSharedGpuResources();
1093 unregisterBindlessTexture(
static_cast<GpuTexture *
>(oldHandle));
1095 owned = std::move(gpu);
1103 registerBindlessTexture2D(
owned.get());
1104 invalidateTextureBindings();
1117 ownedGpuTextures.push_back(std::move(gpu));
1122 ASSERT(!filename.empty());
1123 if (filename.empty())
throw Exception(
"newTextureFromFile: empty filename");
1125 const std::string
key = normalizeTexPath(filename);
1127 throw Exception(
"Could not load image file: %s", filename.c_str());
1129 auto it = texturesByPath.find(
key);
1130 if (it != texturesByPath.end() && it->second) {
1132 if (it->second->hasDeferredFilePixels())
return it->second;
1134 if (!waited.ok())
throw Exception(
"%s", waited.status().describe().c_str());
1136 if (!data || !replaceTexturePixels(it->second, data))
1137 throw Exception(
"newTextureFromFile: reload failed '%s'", filename.c_str());
1142 auto tex = std::make_unique<Texture>();
1143 tex->markDeferredFilePixels(
this);
1145 ownedTextures.push_back(std::move(tex));
1146 texturesByPath[
key] = raw;
1152 return replaceTexturePixels(texture, data);
1156 if (filename.empty())
return false;
1157 const std::string
key = normalizeTexPath(filename);
1158 auto it = texturesByPath.find(
key);
1159 if (it == texturesByPath.end() || !it->second)
return false;
1167 auto *imgMod = image::Image::create();
1168 data = imgMod->newImageDataFromFile(filename);
1169 if (data !=
nullptr) {
1171 if (!
pinned.ok())
return false;
1172 keepAlive = std::move(
pinned).takeValue();
1180 if (!data)
return false;
1181 return replaceTexturePixels(it->second, data);
Stable, structured diagnostics shared by engine modules.
vk::UniqueSampler sampler
std::vector< std::shared_ptr< DeviceBytes > > bindings
std::unique_ptr< gpgpu::GpuBuffer > buffer
Move-only, checked operation results for the common layer.
const UnitySourceAsset & source
const AssetImportLimits & limits
const VegetationPresetContext & context
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.
EVENGINE_API_FOUNDATION public API.
eve::Result< ResourcePin > pin(Resource *resource)
Keeps one cached resource alive across unload()/clear()/reload.
ResultRef< Resource > waitFor(std::string key)
Block until key is cached, failed, or unclaimed.
static ResourceManager & getInstance()
Returns the instance.
bool ok() const noexcept
Whether this result represents a non-failure outcome.
Move-only operation result carrying either a value or Status.
static Result success(T value)
Construct a successful result owning value.
static Result failure(Status status)
Construct a failed result from a structured status.
virtual bool updateTexture(Texture *texture, int width, int height, const uint8_t *rgba)=0
Replace an existing texture's pixels in place (pointer stays stable).
virtual bool copyHDRCanvasesToCubemap(Canvas *const *sources, int faceCount, Texture *cubemap)
Copy consecutive RGBA16F canvases into cubemap base-level faces.
std::vector< DeferredFileTexture > deferredFileTextures_
virtual bool releaseTexture(Texture *texture)
Eagerly releases a texture created by this Graphics.
void push(bool all)
Pushes .
virtual void setTextureSampler(Texture *texture, const std::string &filter, const std::string &mipmap, float maxAnisotropy, float lodBias)
Update sampler state without re-uploading pixels (filter / mip / aniso / LOD bias)....
virtual bool copyHDRCanvasToCubemapFace(Canvas *source, Texture *cubemap, int face)
Copy one RGBA16F Canvas into a staging cubemap base-level face.
virtual eve::Result< void > updateTextureFromResidentRgba8(Texture *texture, const GpuResidentBufferView &source, int width, int height)
Copy a same-device resident RGBA8 buffer into an existing texture.
bool fileTextureSourceExists(const std::string &filename) const
File texture source exists.
void ensureFileTexturesReady()
Finish CPU decode and GPU upload for outstanding newTextureFromFile results. @thread Game/render thre...
virtual bool filterHDRReflectionCubemap(Texture *cubemap, int sampleCount=64)
Generate GGX specular mips and final diffuse irradiance for an HDR cubemap. @compatibility Preserves ...
virtual Texture * newTexture(int width, int height, const uint8_t *rgba, bool repeatU=false, bool repeatV=false)=0
Creates a texture. @ownership Caller deletes unless documented otherwise.
virtual bool reloadTextureFromFile(const std::string &filename)=0
Reload a path-cached texture from disk in place (pointer stable). False if unbound.
virtual Texture * newTextureFromFile(const std::string &filename)=0
Creates a texture from file. @ownership Caller deletes unless documented otherwise.
void requestFileImageDecode(const std::string &key)
Request file image decode.
virtual eve::Result< void > updateTextureRegions(Texture *texture, std::span< const TextureRegionUpload > regions)=0
Validate then upload multiple independent mip-zero RGBA8 regions as one batch.
virtual bool uploadDeferredFileTexture(Texture *texture, image::ImageData *data)
Uploads deferred file texture.
void dropDeferredFileTexture(Texture *texture)
Drop deferred file texture.
virtual eve::Result< void > updateTextureRegion(Texture *texture, int x, int y, int width, int height, std::span< const std::uint8_t > rgba, std::size_t bytesPerRow=0)=0
Upload one tightly packed or row-strided RGBA8 rectangle into mip level zero.
virtual ResultRef< Texture > newSharedTexture(image::ImageData *data, const std::string &contentKey)
Reuse or upload immutable RGBA8 pixels under a caller-provided content key.
GPU texture created via Graphics::newTexture. Owns GPU resources through an opaque backend handle.
Result< Texture * > newTextureArrayRgba16f(uint32_t width, uint32_t height, uint32_t layers, std::span< const uint16_t > rgbaHalf) override
Creates a texture array rgba 16 f. @ownership Caller deletes unless documented otherwise.
Texture * newHDRCubemap(int faceSize) override
Creates a hdr cubemap. @ownership Caller deletes unless documented otherwise.
Result< Texture * > newTextureMipChain(uint32_t width, uint32_t height, uint32_t levels, std::span< const uint8_t > rgba) override
Creates a texture mip chain. @ownership Caller deletes unless documented otherwise.
Texture * newCubemap(int faceSize, const uint8_t *rgbaFaces) override
Creates a cubemap. @ownership Caller deletes unless documented otherwise.
Texture * newTexture(int width, int height, const uint8_t *rgba, bool repeatU=false, bool repeatV=false) override
Creates a texture. @ownership Caller deletes unless documented otherwise.
Result< Texture * > newTexture3DRgba8(uint32_t width, uint32_t height, uint32_t depth, std::span< const uint8_t > rgba) override
Creates a texture 3 d rgba 8. @ownership Caller deletes unless documented otherwise.
OffscreenCanvas public API.
Represents raw pixel data.
RAII keep-alive for one RuntimeObjectRegistry entry.
T * get() const noexcept
Borrows the pinned object; valid until the pin is released.
void uploadTextureForAllShaderStages(vkb::Device &device, vk::CommandPool commandPool, vk::Queue graphicsQueue, TextureImage &image, uint32_t width, uint32_t height, uint32_t mipLevels, uint32_t layers, const std::vector< uint8_t > &bytes, uint32_t bytesPerPixel=4, uint32_t depth=1)
void recordTextureCopies(vk::CommandBuffer cb, TextureImage &image, vkb::GenericBuffer &staging, uint32_t width, uint32_t height, uint32_t depth, uint32_t mipLevels, uint32_t layers, uint32_t bytesPerPixel=4)
int mipmapCountForSize(int width, int height)
Full mip chain count for a 2D image (including base level).
DiagnosticCode
Stable machine-readable diagnostic codes.
Transient backend buffer slice shared between sibling GPU modules. @ownership Non-owning; nativeHandl...
Options for Graphics::newTexture / newCubemap. When generateMipmaps is true and sampler....
static TextureCreateInfo withMipmaps(bool aniso=true, float maxAniso=16.f)
With mipmaps.
One borrowed RGBA8 source rectangle for a batched texture update. @ownership rgba remains owned by th...
std::span< const std::uint8_t > rgba
Sampler state for a Texture (filter, wrap, mip LOD, anisotropy). Defaults match historical engine beh...
static TextureSampler linear()
Linear.
static TextureSampler linearMipmap()
Trilinear (linear + linear mips). Caller should create the texture with generateMipmaps.