mirror of
https://github.com/barkeser2002/flower.git
synced 2026-09-25 13:36:14 +03:00
774 lines
23 KiB
C++
774 lines
23 KiB
C++
#include "Pch.h"
|
|
#include "TextureManager.h"
|
|
#include "Engine.h"
|
|
#include "AssetSystem.h"
|
|
#include "AssetArchive.h"
|
|
#include <stb/stb_image.h>
|
|
#include <stb/stb_image_resize.h>
|
|
|
|
#pragma warning(disable: 4244)
|
|
|
|
namespace Flower
|
|
{
|
|
std::weak_ptr<GPUImageAsset> EngineTextures::GWhiteTexturePtr = {};
|
|
|
|
const UUID EngineTextures::GWhiteTextureUUID = "0d6e103f-138a-482a-8a28-5116631a2e32";
|
|
const UUID EngineTextures::GGreyTextureUUID = "6caa6c06-3c71-4b36-bb88-e0c577a06c60";
|
|
const UUID EngineTextures::GBlackTextureUUID = "c11cc2f7-3c5d-458d-a2b4-68ebf7612948";
|
|
const UUID EngineTextures::GTranslucentTextureUUID = "e515dc68-4947-4ffe-83c3-1696e3aeaf2d";
|
|
const UUID EngineTextures::GNormalTextureUUID = "1b26e66d-855b-4c1b-b13d-d88305a78c9e";
|
|
const UUID EngineTextures::GDefaultSpecularUUID = "424e83d1-ec41-464d-9733-7fcded599fbb";
|
|
|
|
const UUID EngineTextures::GCloudWeatherUUID = "a27c14c4-2bd5-4d58-b416-801ff2d8c71e";
|
|
const UUID EngineTextures::GCurlNoiseUUID = "987b3e4d-27b7-4d49-bf53-6399a63b61cb";
|
|
|
|
uint32_t EngineTextures::GWhiteTextureId = 0;
|
|
uint32_t EngineTextures::GGreyTextureId = 0;
|
|
uint32_t EngineTextures::GBlackTextureId = 0;
|
|
uint32_t EngineTextures::GTranslucentTextureId = 0;
|
|
uint32_t EngineTextures::GNormalTextureId = 0;
|
|
uint32_t EngineTextures::GDefaultSpecularId = 0;
|
|
uint32_t EngineTextures::GCloudWeatherId = 0;
|
|
uint32_t EngineTextures::GCurlNoiseId = 0;
|
|
|
|
void ImageAssetHeader::buildSnapshotData2D(std::shared_ptr<ImageAssetBin> inBin)
|
|
{
|
|
CHECK(m_depth == 1u && m_binDataUUID == inBin->getBinUUID());
|
|
|
|
if (m_width >= GAssetSnapshotMaxDim || m_height >= GAssetSnapshotMaxDim)
|
|
{
|
|
if (m_width > m_height)
|
|
{
|
|
m_widthSnapShot = GAssetSnapshotMaxDim;
|
|
m_heightSnapShot = m_height / (m_width / GAssetSnapshotMaxDim);
|
|
}
|
|
else if (m_height > m_width)
|
|
{
|
|
m_heightSnapShot = GAssetSnapshotMaxDim;
|
|
m_widthSnapShot = m_width / (m_height / GAssetSnapshotMaxDim);
|
|
}
|
|
else
|
|
{
|
|
m_heightSnapShot = GAssetSnapshotMaxDim;
|
|
m_widthSnapShot = GAssetSnapshotMaxDim;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_heightSnapShot = m_height;
|
|
m_widthSnapShot = m_width;
|
|
}
|
|
|
|
m_snapshotData.resize(m_widthSnapShot * m_heightSnapShot * GAssetTextureChannels);
|
|
|
|
if (m_bHdr)
|
|
{
|
|
std::vector<float> hdrData(m_widthSnapShot * m_heightSnapShot * GAssetTextureChannels);
|
|
|
|
stbir_resize_float(
|
|
(float*)inBin->m_rawData.data(),
|
|
m_width,
|
|
m_height,
|
|
0,
|
|
hdrData.data(),
|
|
m_widthSnapShot,
|
|
m_heightSnapShot,
|
|
0,
|
|
GAssetTextureChannels
|
|
);
|
|
|
|
// Post process tonemapper.
|
|
for (size_t i = 0; i < hdrData.size(); i += GAssetTextureChannels)
|
|
{
|
|
hdrData[i + 0] = std::pow(hdrData[i + 0] / (1.0f + hdrData[i + 0]), 1.0 / 2.2f);
|
|
hdrData[i + 1] = std::pow(hdrData[i + 1] / (1.0f + hdrData[i + 1]), 1.0 / 2.2f);
|
|
hdrData[i + 2] = std::pow(hdrData[i + 2] / (1.0f + hdrData[i + 2]), 1.0 / 2.2f);
|
|
|
|
m_snapshotData[i + 0] = uint8_t(hdrData[i + 0] * 255.0f);
|
|
m_snapshotData[i + 1] = uint8_t(hdrData[i + 1] * 255.0f);
|
|
m_snapshotData[i + 2] = uint8_t(hdrData[i + 2] * 255.0f);
|
|
m_snapshotData[i + 3] = uint8_t(255);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Do srgb convert for all texture, so they will looks same with browser editor.
|
|
stbir_resize_uint8_srgb_edgemode(
|
|
inBin->m_rawData.data(),
|
|
m_width,
|
|
m_height,
|
|
0,
|
|
m_snapshotData.data(),
|
|
m_widthSnapShot,
|
|
m_heightSnapShot,
|
|
0,
|
|
GAssetTextureChannels,
|
|
GAssetTextureChannels - 1,
|
|
STBIR_FLAG_ALPHA_PREMULTIPLIED,
|
|
STBIR_EDGE_CLAMP
|
|
);
|
|
}
|
|
}
|
|
|
|
float getAlphaCoverageRGBA8(const unsigned char* data, uint32_t width, uint32_t height, float scale, int cutoff)
|
|
{
|
|
// float value may no enough for multi add.
|
|
double value = 0.0;
|
|
|
|
// 4 char to 1 uint32_t
|
|
uint32_t* pImg = (uint32_t*)data;
|
|
|
|
// Loop all texture to get coverage alpha data.
|
|
for (uint32_t y = 0; y < height; y++)
|
|
{
|
|
for (uint32_t x = 0; x < width; x++)
|
|
{
|
|
// pImg++ to next pixel 4 char = 1 uint32_t
|
|
uint8_t* pPixel = (uint8_t*)pImg++;
|
|
|
|
// alpha in pixel[3]
|
|
int alpha = (int)(scale * (float)pPixel[3]);
|
|
|
|
if (alpha > 255) { alpha = 255; }
|
|
if (alpha <= cutoff) { continue; }
|
|
|
|
value += alpha;
|
|
}
|
|
}
|
|
return (float)(value / (height * width * 255));
|
|
}
|
|
|
|
void scaleAlpha(unsigned char* data, uint32_t width, uint32_t height, float scale)
|
|
{
|
|
uint32_t* pImg = (uint32_t*)data;
|
|
|
|
for (uint32_t y = 0; y < height; y++)
|
|
{
|
|
for (uint32_t x = 0; x < width; x++)
|
|
{
|
|
uint8_t* pPixel = (uint8_t*)pImg++;
|
|
|
|
int alpha = (int)(scale * (float)pPixel[3]);
|
|
if (alpha > 255) { alpha = 255; }
|
|
|
|
pPixel[3] = alpha;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ImageAssetBin::buildMipmapDataRGBA8(ImageAssetHeader* header, float cutOff)
|
|
{
|
|
float alphaCoverageMip0 = 1.0f;
|
|
m_mipmapData.resize(header->getMipmapCount());
|
|
|
|
// Look no good when convert to linear space do mipmap.
|
|
const bool bSrgb = header->isSRGB();
|
|
|
|
for (size_t mip = 0; mip < m_mipmapData.size(); mip++)
|
|
{
|
|
auto& destMipData = m_mipmapData[mip];
|
|
|
|
uint32_t destWidth = std::max<uint32_t>(header->getWidth() >> mip, 1);
|
|
uint32_t destHeight = std::max<uint32_t>(header->getHeight() >> mip, 1);
|
|
|
|
if (mip == 0)
|
|
{
|
|
destMipData = m_rawData;
|
|
|
|
alphaCoverageMip0 = cutOff < 1.0f
|
|
? getAlphaCoverageRGBA8(destMipData.data(), destWidth, destHeight, 1.0f, (int)(cutOff * 255))
|
|
: 1.0f;
|
|
}
|
|
else // Other mip process.
|
|
{
|
|
const size_t srcMip = mip - 1;
|
|
const auto& srcMipData = m_mipmapData[srcMip];
|
|
|
|
destMipData.resize(destWidth * destHeight * 4);
|
|
|
|
for (size_t y = 0; y < destHeight; y++)
|
|
{
|
|
for (size_t x = 0; x < destWidth; x++)
|
|
{
|
|
// Get src data.
|
|
uint32_t srcWidth = std::max<uint32_t>(header->getWidth() >> srcMip, 1);
|
|
uint32_t srcHeight = std::max<uint32_t>(header->getHeight() >> srcMip, 1);
|
|
|
|
// Clamp src data fetech edge.
|
|
size_t srcX0 = (size_t)std::min<uint32_t>(uint32_t(x * 2 + 0), srcWidth - 1);
|
|
size_t srcX1 = (size_t)std::min<uint32_t>(uint32_t(x * 2 + 1), srcWidth - 1);
|
|
size_t srcY0 = (size_t)std::min<uint32_t>(uint32_t(y * 2 + 0), srcHeight - 1);
|
|
size_t srcY1 = (size_t)std::min<uint32_t>(uint32_t(y * 2 + 1), srcHeight - 1);
|
|
|
|
// Prepare src pixel start pos. * 4 is because per pixel has RGBA four components.
|
|
size_t srcPixelStart[] =
|
|
{
|
|
(srcY0 * srcWidth + srcX0) * 4, // X0Y0
|
|
(srcY0 * srcWidth + srcX1) * 4, // X1Y0
|
|
(srcY1 * srcWidth + srcX0) * 4, // X0Y1
|
|
(srcY1 * srcWidth + srcX1) * 4, // X1Y1
|
|
};
|
|
|
|
// Perpixel own 4 uint8_t pack to one uint32_t value.
|
|
const uint32_t* pSrcData = (const uint32_t*)srcMipData.data();
|
|
|
|
uint32_t R = 0, G = 0, B = 0, A = 0;
|
|
for (size_t i = 0; i < 4; i++)
|
|
{
|
|
R += bSrgb ? srgbToLinear(srcMipData[srcPixelStart[i] + 0]) : srcMipData[srcPixelStart[i] + 0];
|
|
G += bSrgb ? srgbToLinear(srcMipData[srcPixelStart[i] + 1]) : srcMipData[srcPixelStart[i] + 1];
|
|
B += bSrgb ? srgbToLinear(srcMipData[srcPixelStart[i] + 2]) : srcMipData[srcPixelStart[i] + 2];
|
|
|
|
A += srcMipData[srcPixelStart[i] + 3];
|
|
}
|
|
R /= 4; R = bSrgb ? linearToSrgb(R) : R;
|
|
G /= 4; G = bSrgb ? linearToSrgb(G) : G;
|
|
B /= 4; B = bSrgb ? linearToSrgb(B) : B;
|
|
A /= 4;
|
|
|
|
// Store to dest.
|
|
size_t destPixelPosStart = (y * destWidth + x) * 4;
|
|
destMipData[destPixelPosStart + 0] = R;
|
|
destMipData[destPixelPosStart + 1] = G;
|
|
destMipData[destPixelPosStart + 2] = B;
|
|
destMipData[destPixelPosStart + 3] = A;
|
|
}
|
|
}
|
|
|
|
if (alphaCoverageMip0 < 1.0f)
|
|
{
|
|
float ini = 0;
|
|
float fin = 10;
|
|
|
|
float mid;
|
|
float alphaPercentage;
|
|
|
|
// find best alpha coverage for mip-map.
|
|
int iter = 0;
|
|
for (; iter < 50; iter++)
|
|
{
|
|
mid = (ini + fin) / 2;
|
|
alphaPercentage = getAlphaCoverageRGBA8(destMipData.data(), destWidth, destHeight, mid, (int)(cutOff * 255));
|
|
|
|
if (glm::abs(alphaPercentage - alphaCoverageMip0) < .001) { break; }
|
|
if (alphaPercentage > alphaCoverageMip0) { fin = mid; }
|
|
if (alphaPercentage < alphaCoverageMip0) { ini = mid; }
|
|
}
|
|
|
|
scaleAlpha(destMipData.data(), destWidth, destHeight, mid);
|
|
}
|
|
}
|
|
|
|
CHECK(destWidth * destHeight * 4 == destMipData.size());
|
|
}
|
|
}
|
|
|
|
bool ImageAssetHeader::initFromRaw2DLDR(
|
|
const std::filesystem::path& rawPath,
|
|
bool bSRGB,
|
|
float cutOff,
|
|
bool bBuildMipmap)
|
|
{
|
|
setCacheBinData(std::make_shared<ImageAssetBin>(rawPath.filename().string()));
|
|
auto processingImageBin = getBinData<ImageAssetBin>();
|
|
|
|
int32_t texWidth, texHeight, texChannels;
|
|
stbi_uc* pixels = stbi_load(rawPath.string().c_str(), &texWidth, &texHeight, &texChannels, GAssetTextureChannels);
|
|
|
|
if (!pixels)
|
|
{
|
|
LOG_ERROR("Fail to load image {0}.", rawPath.string());
|
|
return false;
|
|
}
|
|
|
|
m_bSrgb = bSRGB;
|
|
m_width = texWidth;
|
|
m_height = texHeight;
|
|
m_depth = 1u;
|
|
|
|
// Copy raw data to bin asset.
|
|
processingImageBin->m_rawData.resize(m_width * m_height * m_depth * GAssetTextureChannels);
|
|
memcpy(processingImageBin->m_rawData.data(), pixels, processingImageBin->m_rawData.size());
|
|
|
|
if (m_bSrgb)
|
|
{
|
|
m_format = size_t(VK_FORMAT_R8G8B8A8_SRGB);
|
|
}
|
|
else
|
|
{
|
|
m_format = size_t(VK_FORMAT_R8G8B8A8_UNORM);
|
|
}
|
|
|
|
uint32_t mipWidth = m_width;
|
|
uint32_t mipHeight = m_height;
|
|
uint32_t mipCount = 0;
|
|
while (true)
|
|
{
|
|
mipCount++;
|
|
|
|
if (mipWidth > 1) { mipWidth >>= 1; }
|
|
if (mipHeight > 1) { mipHeight >>= 1; }
|
|
|
|
if (mipWidth == 1 && mipHeight == 1)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
m_mipmapCount = mipCount;
|
|
|
|
if (bBuildMipmap)
|
|
{
|
|
// NOTE: Current we don't do any gpu compression in runtime, we use toKtx tool if need.
|
|
processingImageBin->buildMipmapDataRGBA8(this, cutOff);
|
|
}
|
|
|
|
// Build snapshot data from bin.
|
|
buildSnapshotData2D(processingImageBin);
|
|
|
|
stbi_image_free(pixels);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ImageAssetHeader::initFromRaw2DHDR(const std::filesystem::path& rawPath, bool bBuildMipmap)
|
|
{
|
|
setCacheBinData(std::make_shared<ImageAssetBin>(rawPath.filename().string()));
|
|
auto processingImageBin = getBinData<ImageAssetBin>();
|
|
|
|
int32_t texWidth, texHeight, texChannels;
|
|
float* pixels = stbi_loadf(
|
|
rawPath.string().c_str(),
|
|
&texWidth, &texHeight, &texChannels, GAssetTextureChannels);
|
|
|
|
if (!pixels)
|
|
{
|
|
LOG_ERROR("Fail to load image {0}.", rawPath.string());
|
|
return false;
|
|
}
|
|
|
|
m_bSrgb = false; // HDR should not in srgb space, which is linear space.
|
|
m_width = texWidth;
|
|
m_height = texHeight;
|
|
m_depth = 1u;
|
|
|
|
// Copy raw data to bin asset.
|
|
processingImageBin->m_rawData.resize(m_width * m_height * m_depth * GAssetTextureChannels * 4); // One float = 4 char
|
|
memcpy(processingImageBin->m_rawData.data(), (uint8_t*)pixels, processingImageBin->m_rawData.size());
|
|
|
|
// HDR Format.
|
|
m_format = size_t(VK_FORMAT_R32G32B32A32_SFLOAT);
|
|
|
|
uint32_t mipWidth = m_width;
|
|
uint32_t mipHeight = m_height;
|
|
uint32_t mipCount = 0;
|
|
|
|
if (!bBuildMipmap)
|
|
{
|
|
mipCount = 1;
|
|
}
|
|
else
|
|
{
|
|
while (true)
|
|
{
|
|
mipCount++;
|
|
|
|
if (mipWidth > 1) { mipWidth >>= 1; }
|
|
if (mipHeight > 1) { mipHeight >>= 1; }
|
|
|
|
if (mipWidth == 1 && mipHeight == 1)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
m_mipmapCount = mipCount;
|
|
|
|
if (bBuildMipmap)
|
|
{
|
|
CHECK_ENTRY();
|
|
|
|
// TODO: HDR mipmap generate.
|
|
// processingImageBin->buildMipmapDataRGBA8(this, cutOff);
|
|
}
|
|
|
|
// Build snapshot data from bin.
|
|
buildSnapshotData2D(processingImageBin);
|
|
|
|
|
|
stbi_image_free(pixels);
|
|
|
|
return true;
|
|
}
|
|
|
|
static std::string getRuntimeUniqueImageAssetName(const std::string& in)
|
|
{
|
|
static size_t GRuntimeId = 0;
|
|
GRuntimeId++;
|
|
return "Flower_ImageAssetId:" + std::to_string(GRuntimeId) + in;
|
|
}
|
|
|
|
GPUImageAsset::GPUImageAsset(
|
|
bool bPersistent,
|
|
GPUImageAsset* fallback,
|
|
VkFormat format,
|
|
const std::string& name,
|
|
uint32_t mipmapCount,
|
|
uint32_t width,
|
|
uint32_t height,
|
|
uint32_t depth)
|
|
: LRUAssetInterface(fallback, bPersistent)
|
|
{
|
|
CHECK(m_image == nullptr && "You must ensure image asset only init once.");
|
|
|
|
VkImageCreateInfo info{};
|
|
info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
|
info.flags = {};
|
|
info.imageType = VK_IMAGE_TYPE_2D;
|
|
info.format = format;
|
|
info.extent.width = width;
|
|
info.extent.height = height;
|
|
info.extent.depth = depth;
|
|
info.arrayLayers = 1;
|
|
info.mipLevels = mipmapCount;
|
|
info.samples = VK_SAMPLE_COUNT_1_BIT;
|
|
info.tiling = VK_IMAGE_TILING_OPTIMAL;
|
|
info.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
|
info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
|
info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
|
|
m_image = VulkanImage::create(
|
|
getRuntimeUniqueImageAssetName(name).c_str(),
|
|
info,
|
|
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
|
}
|
|
|
|
GPUImageAsset::~GPUImageAsset()
|
|
{
|
|
if (!m_bPersistent)
|
|
{
|
|
if (m_bindlessIndex != ~0)
|
|
{
|
|
Bindless::Texture->freeBindlessImpl(
|
|
m_bindlessIndex,
|
|
EngineTextures::GWhiteTexturePtr.lock() ? EngineTextures::GWhiteTexturePtr.lock()->m_image : nullptr);
|
|
}
|
|
}
|
|
|
|
|
|
m_image.reset();
|
|
}
|
|
|
|
void GPUImageAsset::prepareToUpload(RHICommandBufferBase& cmd, VkImageSubresourceRange range)
|
|
{
|
|
CHECK(m_bindlessIndex == ~0);
|
|
m_image->transitionLayout(cmd, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, range);
|
|
}
|
|
|
|
void GPUImageAsset::finishUpload(RHICommandBufferBase& cmd, VkImageSubresourceRange range)
|
|
{
|
|
m_image->transitionLayout(cmd, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, range);
|
|
m_bindlessIndex = Bindless::Texture->updateTextureToBindlessDescriptorSet(m_image->getView(buildBasicImageSubresource()));
|
|
|
|
CHECK(m_bindlessIndex != ~0);
|
|
}
|
|
|
|
void TextureContext::shrinkLRU()
|
|
{
|
|
// Find unused asset and push to lazy destory component.
|
|
size_t sizeReduce = m_lruCache->prune([&](std::shared_ptr<GPUImageAsset> removedAsset) {
|
|
|
|
GEngine->getRuntimeModule<AssetSystem>()->addUnusedAsset(removedAsset);
|
|
});
|
|
LOG_INFO("Texture manager reduce {0} mesh size.", sizeReduce);
|
|
}
|
|
|
|
void TextureContext::init()
|
|
{
|
|
m_lruCache = std::make_unique<LRUAssetCache<GPUImageAsset>>(1024, 512);
|
|
}
|
|
|
|
void TextureContext::release()
|
|
{
|
|
m_lruCache.reset();
|
|
}
|
|
|
|
std::shared_ptr<GPUImageAsset> TextureContext::getOrCreateLRUSnapShot(std::shared_ptr<ImageAssetHeader> asset)
|
|
{
|
|
const auto& snapShotUUID = asset->getSnapShotUUID();
|
|
if (!m_lruCache->contain(snapShotUUID))
|
|
{
|
|
auto newTask = SnapshotAssetTextureLoadTask::build(asset);
|
|
GEngine->getRuntimeModule<AssetSystem>()->addUploadTask(newTask);
|
|
}
|
|
|
|
CHECK(m_lruCache->contain(snapShotUUID));
|
|
return m_lruCache->tryGet(snapShotUUID);
|
|
}
|
|
|
|
std::shared_ptr<GPUImageAsset> TextureContext::getOrCreateImage(std::shared_ptr<ImageAssetHeader> asset)
|
|
{
|
|
const auto& imageUUID = asset->getHeaderUUID();
|
|
if (!m_lruCache->contain(imageUUID))
|
|
{
|
|
auto newTask = ImageAssetTextureLoadTask::build(asset);
|
|
GEngine->getRuntimeModule<AssetSystem>()->addUploadTask(newTask);
|
|
}
|
|
|
|
CHECK(m_lruCache->contain(imageUUID));
|
|
return m_lruCache->tryGet(imageUUID);
|
|
}
|
|
|
|
void RawAssetTextureLoadTask::uploadFunction(uint32_t stageBufferOffset, void* mapped, RHICommandBufferBase& commandBuffer, VulkanBuffer& stageBuffer)
|
|
{
|
|
CHECK(cacheRawData.size() <= uploadSize());
|
|
memcpy(mapped, cacheRawData.data(), cacheRawData.size());
|
|
|
|
imageAssetGPU->prepareToUpload(commandBuffer, buildBasicImageSubresource());
|
|
|
|
VkBufferImageCopy region{};
|
|
region.bufferOffset = stageBufferOffset;
|
|
region.bufferRowLength = 0;
|
|
region.bufferImageHeight = 0;
|
|
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
region.imageSubresource.mipLevel = 0;
|
|
region.imageSubresource.baseArrayLayer = 0;
|
|
region.imageSubresource.layerCount = 1;
|
|
region.imageOffset = { 0, 0, 0 };
|
|
region.imageExtent = imageAssetGPU->getImage().getExtent();
|
|
|
|
vkCmdCopyBufferToImage(commandBuffer.cmd, stageBuffer, imageAssetGPU->getImage().getImage(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
|
|
|
|
imageAssetGPU->finishUpload(commandBuffer, buildBasicImageSubresource());
|
|
}
|
|
|
|
std::shared_ptr<RawAssetTextureLoadTask> RawAssetTextureLoadTask::build(
|
|
const std::filesystem::path& path,
|
|
const UUID& uuid,
|
|
VkFormat format)
|
|
{
|
|
int32_t texWidth, texHeight, texChannels;
|
|
stbi_uc* pixels = stbi_load(path.string().c_str(), &texWidth, &texHeight, &texChannels, GAssetTextureChannels);
|
|
|
|
if (!pixels)
|
|
{
|
|
LOG_ERROR("Fail to load image {0}.", path.string());
|
|
return nullptr;
|
|
}
|
|
|
|
CHECK(!TextureManager::get()->isAssetExist(uuid) && "Persistent asset has exist, don't register repeatly.");
|
|
auto newAsset = std::shared_ptr<GPUImageAsset>(new GPUImageAsset(
|
|
true, nullptr,
|
|
format,
|
|
path.stem().string(), 1, texWidth, texHeight, 1));
|
|
TextureManager::get()->insertGPUAsset(uuid, newAsset);
|
|
|
|
// Create new task.
|
|
std::shared_ptr<RawAssetTextureLoadTask> newTask = std::make_shared<RawAssetTextureLoadTask>();
|
|
newTask->imageAssetGPU = newAsset;
|
|
|
|
// Prepare upload data.
|
|
newTask->cacheRawData.resize(texWidth * texHeight * 1 * GAssetTextureChannels);
|
|
memcpy(newTask->cacheRawData.data(), pixels, newTask->cacheRawData.size());
|
|
|
|
// NOTE: GPU Memory align, which make small texture size min is 512 byte. And may size no equal.
|
|
// But at least one thing is guarantee is that cache data size must less than upload Size.
|
|
CHECK(newTask->cacheRawData.size() <= newTask->uploadSize());
|
|
|
|
stbi_image_free(pixels);
|
|
return newTask;
|
|
}
|
|
|
|
std::shared_ptr<RawAssetTextureLoadTask> RawAssetTextureLoadTask::buildFlatTexture(
|
|
const std::string& name,
|
|
const UUID& uuid,
|
|
const glm::uvec4& color,
|
|
const glm::uvec3& size,
|
|
VkFormat format)
|
|
{
|
|
CHECK(!TextureManager::get()->isAssetExist(uuid) && "Persistent asset has exist, don't register repeatly.");
|
|
auto newAsset = std::shared_ptr<GPUImageAsset>(new GPUImageAsset(
|
|
true, nullptr,
|
|
format,
|
|
name, 1, size.x, size.y, size.z));
|
|
TextureManager::get()->insertGPUAsset(uuid, newAsset);
|
|
|
|
// Create new task.
|
|
std::shared_ptr<RawAssetTextureLoadTask> newTask = std::make_shared<RawAssetTextureLoadTask>();
|
|
newTask->imageAssetGPU = newAsset;
|
|
|
|
// Prepare upload data.
|
|
newTask->cacheRawData.resize(size.x * size.y * size.z * GAssetTextureChannels);
|
|
for (size_t i = 0; i < newTask->cacheRawData.size(); i += GAssetTextureChannels)
|
|
{
|
|
newTask->cacheRawData[i + 0] = uint8_t(color.x);
|
|
newTask->cacheRawData[i + 1] = uint8_t(color.y);
|
|
newTask->cacheRawData[i + 2] = uint8_t(color.z);
|
|
newTask->cacheRawData[i + 3] = uint8_t(color.w);
|
|
}
|
|
// NOTE: GPU Memory align, which make small texture size min is 512 byte. And may size no equal.
|
|
// But at least one thing is guarantee is that cache data size must less than upload Size.
|
|
CHECK(newTask->cacheRawData.size() <= newTask->uploadSize());
|
|
return newTask;
|
|
}
|
|
|
|
void SnapshotAssetTextureLoadTask::uploadFunction(
|
|
uint32_t stageBufferOffset,
|
|
void* mapped,
|
|
RHICommandBufferBase& commandBuffer,
|
|
VulkanBuffer& stageBuffer)
|
|
{
|
|
memcpy(mapped, cacheHeader->getSnapShotData().data(), uploadSize());
|
|
|
|
imageAssetGPU->prepareToUpload(commandBuffer, buildBasicImageSubresource());
|
|
|
|
VkBufferImageCopy region{};
|
|
region.bufferOffset = stageBufferOffset;
|
|
region.bufferRowLength = 0;
|
|
region.bufferImageHeight = 0;
|
|
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
region.imageSubresource.mipLevel = 0;
|
|
region.imageSubresource.baseArrayLayer = 0;
|
|
region.imageSubresource.layerCount = 1;
|
|
region.imageOffset = { 0, 0, 0 };
|
|
region.imageExtent = imageAssetGPU->getImage().getExtent();
|
|
|
|
vkCmdCopyBufferToImage(commandBuffer.cmd, stageBuffer, imageAssetGPU->getImage().getImage(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
|
|
|
|
imageAssetGPU->finishUpload(commandBuffer, buildBasicImageSubresource());
|
|
}
|
|
|
|
std::shared_ptr<SnapshotAssetTextureLoadTask> SnapshotAssetTextureLoadTask::build(std::shared_ptr<ImageAssetHeader> inHeader)
|
|
{
|
|
auto* fallbackWhite = TextureManager::get()->getImage(EngineTextures::GWhiteTextureUUID).get();
|
|
CHECK(fallbackWhite && "Fallback texture must be valid, you forget init engine texture before init.");
|
|
|
|
std::shared_ptr<GPUImageAsset> newAsset = std::shared_ptr<GPUImageAsset>(new GPUImageAsset(
|
|
false,
|
|
fallbackWhite,
|
|
VK_FORMAT_R8G8B8A8_UNORM, // All snapshot is unorm.
|
|
inHeader->getName(),
|
|
1,
|
|
inHeader->getSnapShotWidth(),
|
|
inHeader->getSnapShotHeight(),
|
|
1
|
|
));
|
|
|
|
// Register on LRU cache.
|
|
TextureManager::get()->insertGPUAsset(inHeader->getSnapShotUUID(), newAsset);
|
|
|
|
auto newTask = std::make_shared<SnapshotAssetTextureLoadTask>(inHeader);
|
|
newTask->imageAssetGPU = newAsset;
|
|
return newTask;
|
|
}
|
|
|
|
|
|
void ImageAssetTextureLoadTask::uploadFunction(
|
|
uint32_t stageBufferOffset,
|
|
void* mapped,
|
|
RHICommandBufferBase& commandBuffer,
|
|
VulkanBuffer& stageBuffer)
|
|
{
|
|
auto texBin = std::dynamic_pointer_cast<ImageAssetBin>(cacheHeader->loadBinData());
|
|
CHECK(texBin != nullptr);
|
|
|
|
VkImageSubresourceRange rangeAllMips = buildBasicImageSubresource();
|
|
rangeAllMips.levelCount = cacheHeader->getMipmapCount();
|
|
|
|
imageAssetGPU->prepareToUpload(commandBuffer, rangeAllMips);
|
|
|
|
uint32_t bufferOffset = 0;
|
|
uint32_t bufferSize = 0;
|
|
|
|
VkBufferImageCopy region{};
|
|
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
region.imageSubresource.baseArrayLayer = 0;
|
|
region.imageSubresource.layerCount = 1;
|
|
region.imageOffset = { 0, 0, 0 };
|
|
region.bufferOffset = 0;
|
|
region.bufferRowLength = 0;
|
|
region.bufferImageHeight = 0;
|
|
|
|
std::vector<VkBufferImageCopy> copyRegions{};
|
|
|
|
if (texBin->getMipmapDatas().empty())
|
|
{
|
|
// No mipmap, load from src.
|
|
const auto& srcpDatas = texBin->getRawDatas();
|
|
|
|
const uint32_t currentMipSize = (uint32_t)srcpDatas.size();
|
|
|
|
uint32_t mipWidth = cacheHeader->getWidth();
|
|
uint32_t mipHeight = cacheHeader->getHeight();
|
|
|
|
memcpy((void*)((char*)mapped + bufferOffset), srcpDatas.data(), currentMipSize);
|
|
|
|
region.bufferOffset = stageBufferOffset + bufferOffset;
|
|
region.imageSubresource.mipLevel = 0;
|
|
region.imageExtent = { mipWidth, mipHeight, 1 };
|
|
|
|
copyRegions.push_back(region);
|
|
|
|
bufferOffset += currentMipSize;
|
|
bufferSize += currentMipSize;
|
|
}
|
|
else
|
|
{
|
|
const auto& mipmapDatas = texBin->getMipmapDatas();
|
|
for (uint32_t level = 0; level < cacheHeader->getMipmapCount(); level++)
|
|
{
|
|
const auto& currentMip = mipmapDatas.at(level);
|
|
const uint32_t currentMipSize = (uint32_t)currentMip.size();
|
|
|
|
uint32_t mipWidth = std::max<uint32_t>(cacheHeader->getWidth() >> level, 1);
|
|
uint32_t mipHeight = std::max<uint32_t>(cacheHeader->getHeight() >> level, 1);
|
|
|
|
memcpy((void*)((char*)mapped + bufferOffset), currentMip.data(), currentMipSize);
|
|
|
|
region.bufferOffset = stageBufferOffset + bufferOffset;
|
|
region.imageSubresource.mipLevel = level;
|
|
region.imageExtent = { mipWidth, mipHeight, 1 };
|
|
|
|
copyRegions.push_back(region);
|
|
|
|
bufferOffset += currentMipSize;
|
|
bufferSize += currentMipSize;
|
|
}
|
|
}
|
|
|
|
|
|
CHECK(uploadSize() >= bufferSize);
|
|
|
|
vkCmdCopyBufferToImage(commandBuffer.cmd, stageBuffer, imageAssetGPU->getImage().getImage(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, (uint32_t)copyRegions.size(), copyRegions.data());
|
|
|
|
imageAssetGPU->finishUpload(commandBuffer, rangeAllMips);
|
|
}
|
|
|
|
std::shared_ptr<ImageAssetTextureLoadTask> ImageAssetTextureLoadTask::build(
|
|
std::shared_ptr<ImageAssetHeader> inHeader)
|
|
{
|
|
auto* fallbackWhite = TextureManager::get()->getImage(EngineTextures::GWhiteTextureUUID).get();
|
|
CHECK(fallbackWhite && "Fallback texture must be valid, you forget init engine texture before init.");
|
|
|
|
std::shared_ptr<GPUImageAsset> newAsset = std::shared_ptr<GPUImageAsset>(new GPUImageAsset(
|
|
false,
|
|
fallbackWhite,
|
|
inHeader->getFormat(),
|
|
inHeader->getName(),
|
|
inHeader->getMipmapCount(),
|
|
inHeader->getWidth(),
|
|
inHeader->getHeight(),
|
|
1
|
|
));
|
|
|
|
// Register on LRU cache.
|
|
TextureManager::get()->insertGPUAsset(inHeader->getHeaderUUID(), newAsset);
|
|
|
|
auto newTask = std::make_shared<ImageAssetTextureLoadTask>(inHeader);
|
|
newTask->imageAssetGPU = newAsset;
|
|
return newTask;
|
|
}
|
|
|
|
|
|
} |