We got exr loading

This commit is contained in:
2026-01-22 02:04:11 +01:00
parent a5b26f3bdd
commit 41ed926409
23 changed files with 529 additions and 283 deletions

View File

@@ -28,6 +28,11 @@ void OrbitAndSpin::Randomize(uint32_t seed)
std::uniform_real_distribution<float> spinSpeedDist(0.5f, 6.0f);
std::uniform_real_distribution<float> phaseDist(0.0f, 6.28318530718f);
// grow randomness
std::uniform_real_distribution<float> growMinDist(0.03f, 0.08f);
std::uniform_real_distribution<float> growMaxDist(0.2f, 0.4f);
std::uniform_real_distribution<float> growSpeedDist(0.5f, 2.5f);
m_OrbitAxis = RandomUnitVector(rng);
m_SpinAxis = RandomUnitVector(rng);
@@ -35,6 +40,15 @@ void OrbitAndSpin::Randomize(uint32_t seed)
m_SpinSpeed = spinSpeedDist(rng);
m_OrbitPhase = phaseDist(rng);
m_GrowMin = growMinDist(rng);
m_GrowMax = growMaxDist(rng);
m_GrowSpeed = growSpeedDist(rng);
m_GrowPhase = phaseDist(rng); // random start offset
// safety: ensure min < max
if (m_GrowMin > m_GrowMax)
std::swap(m_GrowMin, m_GrowMax);
BuildOrbitBasis();
}
@@ -70,19 +84,27 @@ void OrbitAndSpin::Update()
// grow (always positive)
m_GrowPhase += m_GrowSpeed * dt;
float t = 0.5f * (std::sin(m_GrowPhase) + 1.0f); // 0..1
float s = 1.50 + t * 0.70f; // 0.05..0.15 (pick what you want)
m_GrowPhase += m_GrowSpeed * dt;
GetTransform().SetLocalScale(glm::vec3(std::sin(m_GrowPhase)));
// 0..1
float t = 0.5f * (std::sin(m_GrowPhase) + 1.0f);
// random per-object range
float s = glm::mix(m_GrowMin, m_GrowMax, t);
// respect original scale
GetTransform().SetLocalScale(glm::vec3(s));
// GetTransform().SetLocalScale(glm::vec3(std::sin(m_GrowPhase)));
// material color
// auto& mat = GameState::GetInstance().Renderer().getMaterialMutable(m_MaterialID);
// mat.baseColor = glm::vec3(
// 0.5f + 0.5f * std::sin(m_OrbitAngle * 2.0f),
// 0.5f + 0.5f * std::sin(m_OrbitAngle * 3.0f + 2.0f),
// 0.5f + 0.5f * std::sin(m_OrbitAngle * 4.0f + 4.0f)
// );
// GameState::GetInstance().Renderer().updateMaterialGPU(m_MaterialID);
auto& mat = GameState::GetInstance().Renderer().getMaterialMutable(m_MaterialID);
mat.baseColor = glm::vec3(
0.5f + 0.5f * std::sin(m_OrbitAngle * 2.0f),
0.5f + 0.5f * std::sin(m_OrbitAngle * 3.0f + 2.0f),
0.5f + 0.5f * std::sin(m_OrbitAngle * 4.0f + 4.0f)
);
GameState::GetInstance().Renderer().updateMaterialGPU(m_MaterialID);
}
void OrbitAndSpin::Start() {

View File

@@ -104,9 +104,8 @@ void GfxDevice::init(SDL_Window* window, const std::string& appName, bool vSync)
VkPhysicalDeviceProperties props{};
vkGetPhysicalDeviceProperties(physicalDevice, &props);
imageCache.bindlessSetManager.init(device, props.limits.maxSamplerAnisotropy);
{ // create white texture
imageCache.bindlessSetManager.init(device, props.limits.maxSamplerAnisotropy); {
// create white texture
std::uint32_t pixel = 0xFFFFFFFF;
whiteImageId = createImage(
{
@@ -170,7 +169,7 @@ VulkanImmediateExecutor& GfxDevice::GetImmediateExecuter() {
}
void GfxDevice::endFrame(VkCommandBuffer cmd, const GPUImage& drawImage, const EndFrameProps& props) {
// get swapchain image
// get swapchain image
const auto [swapchainImage, swapchainImageIndex] = swapchain.acquireNextImage(getCurrentFrameIndex());
if (swapchainImage == VK_NULL_HANDLE) {
spdlog::info("Swapchain is freaky, skipping frame...");
@@ -180,9 +179,7 @@ void GfxDevice::endFrame(VkCommandBuffer cmd, const GPUImage& drawImage, const E
// Fences are reset here to prevent the deadlock in case swapchain becomes dirty
swapchain.resetFences(getCurrentFrameIndex());
auto swapchainLayout = VK_IMAGE_LAYOUT_UNDEFINED;
{
auto swapchainLayout = VK_IMAGE_LAYOUT_UNDEFINED; {
const VkImageSubresourceRange clearRange = vkinit::imageSubresourceRange(VK_IMAGE_ASPECT_COLOR_BIT);
vkutil::transitionImage(cmd, swapchainImage, swapchainLayout, VK_IMAGE_LAYOUT_GENERAL);
swapchainLayout = VK_IMAGE_LAYOUT_GENERAL;
@@ -277,8 +274,7 @@ void GfxDevice::immediateSubmit(ImmediateExecuteFunction&& f) const {
GPUBuffer GfxDevice::createBuffer(
std::size_t allocSize,
VkBufferUsageFlags usage,
VmaMemoryUsage memoryUsage) const
{
VmaMemoryUsage memoryUsage) const {
const auto bufferInfo = VkBufferCreateInfo{
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.size = allocSize,
@@ -305,8 +301,7 @@ GPUBuffer GfxDevice::createBuffer(
return buffer;
}
VkDeviceAddress GfxDevice::getBufferAddress(const GPUBuffer& buffer) const
{
VkDeviceAddress GfxDevice::getBufferAddress(const GPUBuffer& buffer) const {
const auto deviceAdressInfo = VkBufferDeviceAddressInfo{
.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO,
.buffer = buffer.buffer,
@@ -314,10 +309,10 @@ VkDeviceAddress GfxDevice::getBufferAddress(const GPUBuffer& buffer) const
return vkGetBufferDeviceAddress(device, &deviceAdressInfo);
}
void GfxDevice::destroyBuffer(const GPUBuffer& buffer) const
{
void GfxDevice::destroyBuffer(const GPUBuffer& buffer) const {
vmaDestroyBuffer(allocator, buffer.buffer, buffer.allocation);
}
//
// GPUImage GfxDevice::loadImageFromFileRaw(
// const std::filesystem::path& path,
@@ -356,15 +351,20 @@ ImageID GfxDevice::createImage(
const vkutil::CreateImageInfo& createInfo,
const std::string& debugName,
void* pixelData,
ImageID imageId)
{
ImageID imageId) {
auto image = createImageRaw(createInfo);
if (!debugName.empty()) {
vkutil::addDebugLabel(device, image.image, debugName.c_str());
image.debugName = debugName;
}
if (pixelData) {
uploadImageData(image, pixelData);
const std::size_t bytes =
std::size_t(image.extent.width) *
std::size_t(image.extent.height) *
std::size_t(image.extent.depth) *
BytesPerTexel(image.format);
uploadImageDataSized(image, pixelData, bytes, 0);
}
if (imageId != NULL_IMAGE_ID) {
return imageCache.addImage(imageId, std::move(image));
@@ -377,8 +377,7 @@ ImageID GfxDevice::createDrawImage(
VkFormat format,
glm::ivec2 size,
const std::string& debugName,
ImageID imageId)
{
ImageID imageId) {
assert(size.x > 0 && size.y > 0);
const auto extent = VkExtent3D{
.width = (std::uint32_t)size.x,
@@ -400,29 +399,21 @@ ImageID GfxDevice::createDrawImage(
return createImage(createImageInfo, debugName, nullptr, imageId);
}
ImageID GfxDevice::loadImageFromFile(
const std::filesystem::path& path,
VkFormat format,
VkImageUsageFlags usage,
bool mipMap)
{
return imageCache.loadImageFromFile(path, format, usage, mipMap);
ImageID GfxDevice::loadImageFromFile(const std::filesystem::path& path, VkImageUsageFlags usage, bool mipMap, TextureIntent intent) {
return imageCache.loadImageFromFile(path, usage, mipMap, intent);
}
const GPUImage& GfxDevice::getImage(ImageID id) const
{
const GPUImage& GfxDevice::getImage(ImageID id) const {
return imageCache.getImage(id);
}
ImageID GfxDevice::addImageToCache(GPUImage img)
{
ImageID GfxDevice::addImageToCache(GPUImage img) {
return imageCache.addImage(std::move(img));
}
GPUImage GfxDevice::createImageRaw(
const vkutil::CreateImageInfo& createInfo,
std::optional<VmaAllocationCreateInfo> customAllocationCreateInfo) const
{
std::optional<VmaAllocationCreateInfo> customAllocationCreateInfo) const {
std::uint32_t mipLevels = 1;
if (createInfo.mipMap) {
const auto maxExtent = std::max(createInfo.extent.width, createInfo.extent.height);
@@ -452,9 +443,7 @@ GPUImage GfxDevice::createImageRaw(
.usage = VMA_MEMORY_USAGE_AUTO,
.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
};
const auto allocInfo = customAllocationCreateInfo.has_value() ?
customAllocationCreateInfo.value() :
defaultAllocInfo;
const auto allocInfo = customAllocationCreateInfo.has_value() ? customAllocationCreateInfo.value() : defaultAllocInfo;
GPUImage image{};
image.format = createInfo.format;
@@ -464,8 +453,7 @@ GPUImage GfxDevice::createImageRaw(
image.numLayers = createInfo.numLayers;
image.isCubemap = createInfo.isCubemap;
VK_CHECK(
vmaCreateImage(allocator, &imgInfo, &allocInfo, &image.image, &image.allocation, nullptr));
VK_CHECK(vmaCreateImage(allocator, &imgInfo, &allocInfo, &image.image, &image.allocation, nullptr));
// create view only when usage flags allow it
bool shouldCreateView = ((createInfo.usage & VK_IMAGE_USAGE_SAMPLED_BIT) != 0) ||
@@ -475,12 +463,13 @@ GPUImage GfxDevice::createImageRaw(
if (shouldCreateView) {
VkImageAspectFlags aspectFlag = VK_IMAGE_ASPECT_COLOR_BIT;
if (createInfo.format == VK_FORMAT_D32_SFLOAT) { // TODO: support other depth formats
if (createInfo.format == VK_FORMAT_D32_SFLOAT) {
// TODO: support other depth formats
aspectFlag = VK_IMAGE_ASPECT_DEPTH_BIT;
}
auto viewType =
createInfo.numLayers == 1 ? VK_IMAGE_VIEW_TYPE_2D : VK_IMAGE_VIEW_TYPE_2D_ARRAY;
createInfo.numLayers == 1 ? VK_IMAGE_VIEW_TYPE_2D : VK_IMAGE_VIEW_TYPE_2D_ARRAY;
if (createInfo.isCubemap && createInfo.numLayers == 6) {
viewType = VK_IMAGE_VIEW_TYPE_CUBE;
}
@@ -491,13 +480,13 @@ GPUImage GfxDevice::createImageRaw(
.viewType = viewType,
.format = createInfo.format,
.subresourceRange =
VkImageSubresourceRange{
.aspectMask = aspectFlag,
.baseMipLevel = 0,
.levelCount = mipLevels,
.baseArrayLayer = 0,
.layerCount = createInfo.numLayers,
},
VkImageSubresourceRange{
.aspectMask = aspectFlag,
.baseMipLevel = 0,
.levelCount = mipLevels,
.baseArrayLayer = 0,
.layerCount = createInfo.numLayers,
},
};
VK_CHECK(vkCreateImageView(device, &viewCreateInfo, nullptr, &image.imageView));
@@ -506,104 +495,176 @@ GPUImage GfxDevice::createImageRaw(
return image;
}
void GfxDevice::uploadImageData(const GPUImage& image, void* pixelData, std::uint32_t layer) const
{
VkDeviceSize dataSize =
VkDeviceSize(image.extent.depth) *
image.extent.width *
image.extent.height *
BytesPerTexel(image.format);
auto uploadBuffer = createBuffer(dataSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
memcpy(uploadBuffer.info.pMappedData, pixelData, size_t(dataSize));
executor.immediateSubmit([&](VkCommandBuffer cmd) {
assert(
(image.usage & VK_IMAGE_USAGE_TRANSFER_DST_BIT) != 0 &&
"Image needs to have VK_IMAGE_USAGE_TRANSFER_DST_BIT to upload data to it");
vkutil::transitionImage(
cmd, image.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
const auto copyRegion = VkBufferImageCopy{
.bufferOffset = 0,
.bufferRowLength = 0,
.bufferImageHeight = 0,
.imageSubresource =
{
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.mipLevel = 0,
.baseArrayLayer = layer,
.layerCount = 1,
},
.imageExtent = image.extent,
};
vkCmdCopyBufferToImage(
cmd,
uploadBuffer.buffer,
image.image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
1,
&copyRegion);
if (image.mipLevels > 1) {
assert(
(image.usage & VK_IMAGE_USAGE_TRANSFER_DST_BIT) != 0 &&
(image.usage & VK_IMAGE_USAGE_TRANSFER_SRC_BIT) != 0 &&
"Image needs to have VK_IMAGE_USAGE_TRANSFER_{DST,SRC}_BIT to generate mip maps");
// graphics::generateMipmaps(
// cmd,
// image.image,
// VkExtent2D{image.extent.width, image.extent.height},
// image.mipLevels);
spdlog::warn("Yea dawg, i ain't written this yet :pray:");
} else {
vkutil::transitionImage(
cmd,
image.image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
}
});
destroyBuffer(uploadBuffer);
}
GPUImage GfxDevice::loadImageFromFileRaw(
const std::filesystem::path& path,
VkFormat format,
VkImageUsageFlags usage,
bool mipMap) const
{
auto data = util::loadImage(path);
if (!data.pixels) {
fmt::println("[error] failed to load image from '{}'", path.string());
bool mipMap,
TextureIntent intent) const {
// 1) Decode file to CPU pixels (stb for png/jpg/hdr, tinyexr for exr)
// IMPORTANT: util::loadImage should fill:
// - width/height/channels(=4)
// - hdr + (pixels OR hdrPixels)
// - vkFormat (RGBA8_SRGB or RGBA8_UNORM or RGBA32F)
// - byteSize (exact bytes to upload)
const auto data = util::loadImage(path, intent);
if (data.vkFormat == VK_FORMAT_UNDEFINED || data.byteSize == 0 ||
(data.hdr ? (data.hdrPixels == nullptr) : (data.pixels == nullptr))) {
spdlog::error("Failed to load image '{}'", path.string());
return getImage(errorImageId);
}
// 2) Create GPU image using the format the loader chose (matches CPU memory layout)
auto image = createImageRaw({
.format = format,
.usage = usage | //
VK_IMAGE_USAGE_TRANSFER_DST_BIT | // for uploading pixel data to image
VK_IMAGE_USAGE_TRANSFER_SRC_BIT, // for generating mips
.extent =
VkExtent3D{
.width = (std::uint32_t)data.width,
.height = (std::uint32_t)data.height,
.depth = 1,
},
.format = data.vkFormat,
.usage = usage |
VK_IMAGE_USAGE_TRANSFER_DST_BIT |
(mipMap ? VK_IMAGE_USAGE_TRANSFER_SRC_BIT : 0),
.extent = VkExtent3D{
.width = static_cast<std::uint32_t>(data.width),
.height = static_cast<std::uint32_t>(data.height),
.depth = 1,
},
.mipMap = mipMap,
});
uploadImageData(image, data.pixels);
// 3) Upload *exactly* byteSize bytes from the correct pointer
const void* src = data.hdr
? static_cast<const void*>(data.hdrPixels)
: static_cast<const void*>(data.pixels);
// Use the "sized" upload to avoid BytesPerTexel mismatches
uploadImageDataSized(image, src, data.byteSize, 0);
// 4) Debug label
image.debugName = path.string();
vkutil::addDebugLabel(device, image.image, path.string().c_str());
return image;
}
void GfxDevice::destroyImage(const GPUImage& image) const
{
// void GfxDevice::uploadImageData(const GPUImage& image, void* pixelData, std::uint32_t layer) const {
// VkDeviceSize dataSize =
// VkDeviceSize(image.extent.depth) *
// image.extent.width *
// image.extent.height *
// BytesPerTexel(image.format);
//
// auto uploadBuffer = createBuffer(dataSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
// memcpy(uploadBuffer.info.pMappedData, pixelData, size_t(dataSize));
//
// executor.immediateSubmit([&] (VkCommandBuffer cmd) {
// assert(
// (image.usage & VK_IMAGE_USAGE_TRANSFER_DST_BIT) != 0 &&
// "Image needs to have VK_IMAGE_USAGE_TRANSFER_DST_BIT to upload data to it");
// vkutil::transitionImage(
// cmd, image.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
//
// const auto copyRegion = VkBufferImageCopy{
// .bufferOffset = 0,
// .bufferRowLength = 0,
// .bufferImageHeight = 0,
// .imageSubresource =
// {
// .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
// .mipLevel = 0,
// .baseArrayLayer = layer,
// .layerCount = 1,
// },
// .imageExtent = image.extent,
// };
//
// vkCmdCopyBufferToImage(
// cmd,
// uploadBuffer.buffer,
// image.image,
// VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
// 1,
// &copyRegion);
//
// if (image.mipLevels > 1) {
// assert(
// (image.usage & VK_IMAGE_USAGE_TRANSFER_DST_BIT) != 0 &&
// (image.usage & VK_IMAGE_USAGE_TRANSFER_SRC_BIT) != 0 &&
// "Image needs to have VK_IMAGE_USAGE_TRANSFER_{DST,SRC}_BIT to generate mip maps");
// // graphics::generateMipmaps(
// // cmd,
// // image.image,
// // VkExtent2D{image.extent.width, image.extent.height},
// // image.mipLevels);
// spdlog::warn("Yea dawg, i ain't written this yet :pray:");
// } else {
// vkutil::transitionImage(
// cmd,
// image.image,
// VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
// VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
// }
// });
//
// destroyBuffer(uploadBuffer);
// }
void GfxDevice::uploadImageDataSized(const GPUImage& image, const void* pixelData, std::size_t byteSize, std::uint32_t layer) const {
auto uploadBuffer = createBuffer(byteSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VMA_MEMORY_USAGE_CPU_TO_GPU);
// safety checks
assert(uploadBuffer.info.pMappedData);
assert(pixelData);
assert(byteSize > 0);
std::memcpy(uploadBuffer.info.pMappedData, pixelData, byteSize);
executor.immediateSubmit([&] (VkCommandBuffer cmd) {
vkutil::transitionImage(cmd, image.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
VkBufferImageCopy copyRegion{};
copyRegion.bufferOffset = 0;
copyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
copyRegion.imageSubresource.mipLevel = 0;
copyRegion.imageSubresource.baseArrayLayer = layer;
copyRegion.imageSubresource.layerCount = 1;
copyRegion.imageExtent = image.extent;
vkCmdCopyBufferToImage(cmd, uploadBuffer.buffer, image.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &copyRegion);
vkutil::transitionImage(cmd, image.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
});
destroyBuffer(uploadBuffer);
}
// GPUImage GfxDevice::loadImageFromFileRaw(const std::filesystem::path& path, VkImageUsageFlags usage, bool mipMap) const {
// const auto data = util::loadImage(path);
// const bool isHdr = data.hdr && data.hdrPixels;
// const bool isLdr = !data.hdr && data.pixels;
//
// if (!isHdr && !isLdr || data.vkFormat == VK_FORMAT_UNDEFINED || data.byteSize == 0) {
// spdlog::error("failed to load image from '{}'", path.string());
// return getImage(errorImageId);
// }
//
// auto image = createImageRaw({
// .format = data.vkFormat,
// .usage = usage |
// VK_IMAGE_USAGE_TRANSFER_DST_BIT |
// (mipMap ? VK_IMAGE_USAGE_TRANSFER_SRC_BIT : 0),
// .extent = VkExtent3D{
// .width = (std::uint32_t)data.width,
// .height = (std::uint32_t)data.height,
// .depth = 1,
// },
// .mipMap = mipMap,
// });
//
// const void* src = isHdr ? (const void*)data.hdrPixels : (const void*)data.pixels;
// uploadImageDataSized(image, src, data.byteSize, 0);
//
// image.debugName = path.string();
// vkutil::addDebugLabel(device, image.image, path.string().c_str());
// return image;
// }
void GfxDevice::destroyImage(const GPUImage& image) const {
vkDestroyImageView(device, image.imageView, nullptr);
vmaDestroyImage(allocator, image.image, image.allocation);
// TODO: if image has bindless id, update the set

View File

@@ -7,20 +7,21 @@ ImageCache::ImageCache(GfxDevice& gfxDevice) : gfxDevice(gfxDevice)
ImageID ImageCache::loadImageFromFile(
const std::filesystem::path& path,
VkFormat format,
VkImageUsageFlags usage,
bool mipMap)
bool mipMap,
TextureIntent intent)
{
for (const auto& [id, info] : loadedImagesInfo) {
// TODO: calculate some hash to not have to linear search every time?
if (info.path == path && info.format == format && info.usage == usage &&
info.mipMap == mipMap) {
// std::cout << "Already loaded: " << path << std::endl;
if (info.path == path &&
info.intent == intent &&
info.usage == usage &&
info.mipMap == mipMap)
{
return id;
}
}
}
auto image = gfxDevice.loadImageFromFileRaw(path, format, usage, mipMap);
auto image = gfxDevice.loadImageFromFileRaw(path, usage, mipMap, intent);
if (image.isInitialized() && image.getBindlessId() == errorImageId) {
return errorImageId;
}
@@ -32,10 +33,11 @@ ImageID ImageCache::loadImageFromFile(
id,
LoadedImageInfo{
.path = path,
.format = format,
.intent = intent,
.usage = usage,
.mipMap = mipMap,
});
return id;
}
@@ -59,6 +61,7 @@ ImageID ImageCache::addImage(ImageID id, GPUImage image)
const GPUImage& ImageCache::getImage(ImageID id) const
{
assert(id != NULL_IMAGE_ID && id < images.size());
return images.at(id);
}

View File

@@ -2,30 +2,118 @@
#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>
#include <tiny_gltf.h>
#define TINYEXR_IMPLEMENTATION
#include "tinyexr.h"
#include <cstdio> // fprintf
#include <cstdlib> // free
#include "spdlog/spdlog.h"
ImageData::~ImageData()
{
if (shouldSTBFree) {
stbi_image_free(pixels);
stbi_image_free(hdrPixels);
if (pixels) stbi_image_free(pixels);
if (hdrPixels) stbi_image_free(hdrPixels);
spdlog::debug("Freed STB image memory");
}
if (shouldEXRFree) {
if (hdrPixels) free(hdrPixels);
spdlog::debug("Freed TinyEXR image memory");
}
}
namespace util
{
ImageData loadImage(const std::filesystem::path& p)
static bool isExrExt(const std::filesystem::path& p)
{
auto ext = p.extension().string();
for (auto& c : ext) c = char(::tolower(c));
return ext == ".exr";
}
ImageData loadImage(const std::filesystem::path& p, TextureIntent intent)
{
ImageData data;
// ---------- EXR ----------
if (isExrExt(p)) {
float* out = nullptr;
int w = 0, h = 0;
const char* err = nullptr;
const int ret = LoadEXR(&out, &w, &h, p.string().c_str(), &err);
if (ret != TINYEXR_SUCCESS || !out || w <= 0 || h <= 0) {
if (err) {
spdlog::error("TinyEXR error: {}", err);
FreeEXRErrorMessage(err);
}
return {};
}
data.hdr = true;
data.hdrPixels = out;
data.width = w;
data.height = h;
data.channels = 4;
data.shouldEXRFree = true;
data.vkFormat = VK_FORMAT_R32G32B32A32_SFLOAT;
data.byteSize = std::size_t(w) * std::size_t(h) * 4 * sizeof(float);
return data;
}
// ---------- STB HDR (.hdr etc) ----------
data.shouldSTBFree = true;
if (stbi_is_hdr(p.string().c_str())) {
data.hdr = true;
data.hdrPixels = stbi_loadf(p.string().c_str(), &data.width, &data.height, &data.comp, 4);
} else {
data.pixels = stbi_load(p.string().c_str(), &data.width, &data.height, &data.channels, 4);
data.hdrPixels = stbi_loadf(
p.string().c_str(),
&data.width,
&data.height,
&data.comp,
4
);
if (!data.hdrPixels || data.width <= 0 || data.height <= 0) {
return {};
}
data.channels = 4;
data.vkFormat = VK_FORMAT_R32G32B32A32_SFLOAT;
data.byteSize = std::size_t(data.width) * std::size_t(data.height) * 4 * sizeof(float);
return data;
}
// ---------- STB LDR (png/jpg/...) ----------
data.pixels = stbi_load(
p.string().c_str(),
&data.width,
&data.height,
&data.channels,
4
);
if (!data.pixels || data.width <= 0 || data.height <= 0) {
return {};
}
data.channels = 4;
// Choose color space for LDR based on intent
switch (intent) {
case TextureIntent::ColorSrgb:
data.vkFormat = VK_FORMAT_R8G8B8A8_SRGB;
break;
case TextureIntent::DataLinear:
data.vkFormat = VK_FORMAT_R8G8B8A8_UNORM;
break;
}
data.byteSize = std::size_t(data.width) * std::size_t(data.height) * 4 * sizeof(unsigned char);
return data;
}
} // namespace util
} // namespace util

View File

@@ -28,6 +28,7 @@ void GameRenderer::init(GfxDevice& gfxDevice, const glm::ivec2& drawImageSize) {
}
void GameRenderer::beginDrawing(GfxDevice& gfxDevice) {
flushMaterialUpdates(gfxDevice);
meshDrawCommands.clear();
}
@@ -47,8 +48,13 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera&
.fogDensity = sceneData.fogDensity,
.materialsBuffer = materialCache.getMaterialDataBufferAddress(),
};
sceneDataBuffer.uploadNewData(
cmd, gfxDevice.getCurrentFrameIndex(), (void*)&gpuSceneData, sizeof(GPUSceneData));
vkutil::bufferHostWriteToShaderReadBarrier(
cmd,
materialCache.getMaterialDataBuffer().buffer,
0,
VK_WHOLE_SIZE
);
sceneDataBuffer.uploadNewData(cmd, gfxDevice.getCurrentFrameIndex(), (void*)&gpuSceneData, sizeof(GPUSceneData));
const auto& drawImage = gfxDevice.getImage(drawImageId);
const auto& depthImage = gfxDevice.getImage(depthImageId);
@@ -122,7 +128,7 @@ Material& GameRenderer::getMaterialMutable(MaterialID id) {
void GameRenderer::updateMaterialGPU(MaterialID id) {
assert(id != NULL_MATERIAL_ID);
materialCache.updateMaterialGPU(GameState::GetInstance().Gfx(), id);
pendingMaterialUploads.push_back(id);
}
void GameRenderer::setSkyboxTexture(ImageID skyboxImageId) {
@@ -130,6 +136,14 @@ void GameRenderer::setSkyboxTexture(ImageID skyboxImageId) {
skyboxPipeline->setSkyboxImage(skyboxImageId);
}
void GameRenderer::flushMaterialUpdates(GfxDevice& gfxDevice) {
for (MaterialID id : pendingMaterialUploads) {
materialCache.updateMaterialGPU(gfxDevice, id);
// if non-coherent: flush mapped range for that id here
}
pendingMaterialUploads.clear();
}
void GameRenderer::createDrawImage(GfxDevice& gfxDevice,
const glm::ivec2& drawImageSize,
bool firstCreate)

View File

@@ -30,7 +30,8 @@ CubeMap::~CubeMap() {
}
void CubeMap::LoadCubeMap(const std::filesystem::path& directoryPath) {
m_hdrImage = GameState::GetInstance().Gfx().loadImageFromFile(directoryPath, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, false);
// m_hdrImage = GameState::GetInstance().Gfx().loadImageFromFile(directoryPath, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, false);
m_hdrImage = GameState::GetInstance().Gfx().loadImageFromFile(directoryPath, VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, false);
}
void CubeMap::RenderToCubemap(ImageID inputImage,

View File

@@ -10,9 +10,9 @@ void vkutil::transitionImage(VkCommandBuffer cmd, VkImage image, VkImageLayout c
VkImageAspectFlags aspectMask =
(currentLayout == VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL ||
newLayout == VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL ||
newLayout == VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL) ?
VK_IMAGE_ASPECT_DEPTH_BIT :
VK_IMAGE_ASPECT_COLOR_BIT;
newLayout == VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL)
? VK_IMAGE_ASPECT_DEPTH_BIT
: VK_IMAGE_ASPECT_COLOR_BIT;
VkImageMemoryBarrier2 imageBarrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2,
.srcStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
@@ -81,6 +81,27 @@ void vkutil::copyImageToImage(VkCommandBuffer cmd, VkImage source, VkImage desti
vkCmdBlitImage2(cmd, &blitInfo);
}
void vkutil::bufferHostWriteToShaderReadBarrier(
VkCommandBuffer cmd, VkBuffer buffer, VkDeviceSize offset, VkDeviceSize size) {
VkBufferMemoryBarrier2 barrier{VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2};
barrier.srcStageMask = VK_PIPELINE_STAGE_2_HOST_BIT;
barrier.srcAccessMask = VK_ACCESS_2_HOST_WRITE_BIT;
barrier.dstStageMask = VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT |
VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT;
barrier.dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT;
barrier.buffer = buffer;
barrier.offset = offset;
barrier.size = size;
VkDependencyInfo dep{VK_STRUCTURE_TYPE_DEPENDENCY_INFO};
dep.bufferMemoryBarrierCount = 1;
dep.pBufferMemoryBarriers = &barrier;
vkCmdPipelineBarrier2(cmd, &dep);
}
void vkutil::addDebugLabel(VkDevice device, VkImage image, const char* label) {
const auto nameInfo = VkDebugUtilsObjectNameInfoEXT{
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
@@ -135,8 +156,7 @@ vkutil::RenderInfo vkutil::createRenderingInfo(const RenderingInfoParams& params
.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO,
.imageView = params.colorImageView,
.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
.loadOp = params.colorImageClearValue ? VK_ATTACHMENT_LOAD_OP_CLEAR :
VK_ATTACHMENT_LOAD_OP_LOAD,
.loadOp = params.colorImageClearValue ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_LOAD,
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
};
if (params.colorImageClearValue) {
@@ -150,8 +170,7 @@ vkutil::RenderInfo vkutil::createRenderingInfo(const RenderingInfoParams& params
.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO,
.imageView = params.depthImageView,
.imageLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL,
.loadOp = params.depthImageClearValue ? VK_ATTACHMENT_LOAD_OP_CLEAR :
VK_ATTACHMENT_LOAD_OP_LOAD,
.loadOp = params.depthImageClearValue ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_LOAD,
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
};
if (params.depthImageClearValue) {
@@ -162,10 +181,10 @@ vkutil::RenderInfo vkutil::createRenderingInfo(const RenderingInfoParams& params
ri.renderingInfo = VkRenderingInfo{
.sType = VK_STRUCTURE_TYPE_RENDERING_INFO,
.renderArea =
VkRect2D{
.offset = {},
.extent = params.renderExtent,
},
VkRect2D{
.offset = {},
.extent = params.renderExtent,
},
.layerCount = 1,
.colorAttachmentCount = params.colorImageView ? 1u : 0u,
.pColorAttachments = params.colorImageView ? &ri.colorAttachment : nullptr,
@@ -203,8 +222,7 @@ VkShaderModule vkutil::loadShaderModule(const std::filesystem::path& path, VkDev
return shaderModule;
}
void addDebugLabel(VkDevice device, VkImage image, const char* label)
{
void addDebugLabel(VkDevice device, VkImage image, const char* label) {
const auto nameInfo = VkDebugUtilsObjectNameInfoEXT{
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
.objectType = VK_OBJECT_TYPE_IMAGE,
@@ -214,8 +232,7 @@ void addDebugLabel(VkDevice device, VkImage image, const char* label)
vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
}
void addDebugLabel(VkDevice device, VkImageView imageView, const char* label)
{
void addDebugLabel(VkDevice device, VkImageView imageView, const char* label) {
const auto nameInfo = VkDebugUtilsObjectNameInfoEXT{
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
.objectType = VK_OBJECT_TYPE_IMAGE_VIEW,
@@ -225,8 +242,7 @@ void addDebugLabel(VkDevice device, VkImageView imageView, const char* label)
vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
}
void addDebugLabel(VkDevice device, VkShaderModule shaderModule, const char* label)
{
void addDebugLabel(VkDevice device, VkShaderModule shaderModule, const char* label) {
const auto nameInfo = VkDebugUtilsObjectNameInfoEXT{
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
.objectType = VK_OBJECT_TYPE_SHADER_MODULE,
@@ -236,8 +252,7 @@ void addDebugLabel(VkDevice device, VkShaderModule shaderModule, const char* lab
vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
}
void addDebugLabel(VkDevice device, VkPipeline pipeline, const char* label)
{
void addDebugLabel(VkDevice device, VkPipeline pipeline, const char* label) {
const auto nameInfo = VkDebugUtilsObjectNameInfoEXT{
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
.objectType = VK_OBJECT_TYPE_PIPELINE,
@@ -247,8 +262,7 @@ void addDebugLabel(VkDevice device, VkPipeline pipeline, const char* label)
vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
}
void addDebugLabel(VkDevice device, VkPipelineLayout layout, const char* label)
{
void addDebugLabel(VkDevice device, VkPipelineLayout layout, const char* label) {
const auto nameInfo = VkDebugUtilsObjectNameInfoEXT{
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
.objectType = VK_OBJECT_TYPE_PIPELINE_LAYOUT,
@@ -258,8 +272,7 @@ void addDebugLabel(VkDevice device, VkPipelineLayout layout, const char* label)
vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
}
void addDebugLabel(VkDevice device, VkBuffer buffer, const char* label)
{
void addDebugLabel(VkDevice device, VkBuffer buffer, const char* label) {
const auto nameInfo = VkDebugUtilsObjectNameInfoEXT{
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
.objectType = VK_OBJECT_TYPE_BUFFER,
@@ -269,8 +282,7 @@ void addDebugLabel(VkDevice device, VkBuffer buffer, const char* label)
vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
}
void addDebugLabel(VkDevice device, VkSampler sampler, const char* label)
{
void addDebugLabel(VkDevice device, VkSampler sampler, const char* label) {
const auto nameInfo = VkDebugUtilsObjectNameInfoEXT{
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
.objectType = VK_OBJECT_TYPE_SAMPLER,
@@ -280,8 +292,7 @@ void addDebugLabel(VkDevice device, VkSampler sampler, const char* label)
vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
}
vkutil::RenderInfo createRenderingInfo(const vkutil::RenderingInfoParams& params)
{
vkutil::RenderInfo createRenderingInfo(const vkutil::RenderingInfoParams& params) {
assert(
(params.colorImageView || params.depthImageView != nullptr) &&
"Either draw or depth image should be present");
@@ -295,8 +306,7 @@ vkutil::RenderInfo createRenderingInfo(const vkutil::RenderingInfoParams& params
.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO,
.imageView = params.colorImageView,
.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
.loadOp = params.colorImageClearValue ? VK_ATTACHMENT_LOAD_OP_CLEAR :
VK_ATTACHMENT_LOAD_OP_LOAD,
.loadOp = params.colorImageClearValue ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_LOAD,
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
};
if (params.colorImageClearValue) {
@@ -310,8 +320,7 @@ vkutil::RenderInfo createRenderingInfo(const vkutil::RenderingInfoParams& params
.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO,
.imageView = params.depthImageView,
.imageLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL,
.loadOp = params.depthImageClearValue ? VK_ATTACHMENT_LOAD_OP_CLEAR :
VK_ATTACHMENT_LOAD_OP_LOAD,
.loadOp = params.depthImageClearValue ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_LOAD,
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
};
if (params.depthImageClearValue) {
@@ -322,10 +331,10 @@ vkutil::RenderInfo createRenderingInfo(const vkutil::RenderingInfoParams& params
ri.renderingInfo = VkRenderingInfo{
.sType = VK_STRUCTURE_TYPE_RENDERING_INFO,
.renderArea =
VkRect2D{
.offset = {},
.extent = params.renderExtent,
},
VkRect2D{
.offset = {},
.extent = params.renderExtent,
},
.layerCount = 1,
.colorAttachmentCount = params.colorImageView ? 1u : 0u,
.pColorAttachments = params.colorImageView ? &ri.colorAttachment : nullptr,