mirror of
https://github.com/johndoe6345789/SDL3CPlusPlus.git
synced 2026-04-27 07:05:03 +00:00
501 lines
17 KiB
C++
501 lines
17 KiB
C++
//
|
|
// Copyright Contributors to the MaterialX Project
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
//
|
|
|
|
#include <MaterialXRenderSlang/SlangTextureHandler.h>
|
|
#include <MaterialXRenderSlang/SlangProgram.h>
|
|
#include <MaterialXRenderSlang/SlangTypeUtils.h>
|
|
|
|
#include <MaterialXRender/ShaderRenderer.h>
|
|
|
|
#include <iostream>
|
|
#include <cassert>
|
|
|
|
MATERIALX_NAMESPACE_BEGIN
|
|
|
|
SlangTextureHandler::SlangTextureHandler(SlangContextPtr context, ImageLoaderPtr imageLoader) :
|
|
ImageHandler(std::move(imageLoader)), _context(std::move(context)), _device(_context->getDevice())
|
|
{
|
|
getPipeline(1, Image::BaseType::FLOAT);
|
|
getPipeline(1, Image::BaseType::UINT8);
|
|
getPipeline(3, Image::BaseType::FLOAT);
|
|
getPipeline(3, Image::BaseType::UINT8);
|
|
}
|
|
|
|
/// Release rendering resources for the given image, or for all cached images
|
|
/// if no image pointer is specified.
|
|
void SlangTextureHandler::releaseRenderResources(ImagePtr image)
|
|
{
|
|
if (!image)
|
|
{
|
|
for (const auto& iter : _imageCache)
|
|
{
|
|
if (iter.second)
|
|
{
|
|
releaseRenderResources(iter.second);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (image->getResourceId() == SlangProgram::UNDEFINED_SLANG_RESOURCE_ID)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (auto it = _slangTextureMap.find(image->getResourceId()); it != _slangTextureMap.end())
|
|
{
|
|
_slangTextureMap.erase(it);
|
|
}
|
|
if (auto it = _imageBindingInfo.find(image->getResourceId()); it != _imageBindingInfo.end())
|
|
{
|
|
_imageBindingInfo.erase(it);
|
|
}
|
|
image->setResourceId(SlangProgram::UNDEFINED_SLANG_RESOURCE_ID);
|
|
}
|
|
|
|
SlangTexturePtr SlangTextureHandler::getAssociatedSlangTexture(ImagePtr image)
|
|
{
|
|
if (!image)
|
|
return {};
|
|
|
|
if (auto it = _slangTextureMap.find(image->getResourceId()); it != _slangTextureMap.end())
|
|
return it->second;
|
|
|
|
return {};
|
|
}
|
|
|
|
bool SlangTextureHandler::bindImage(ImagePtr image, const ImageSamplingProperties& samplingProperties)
|
|
{
|
|
if (image->getResourceId() == SlangProgram::UNDEFINED_SLANG_RESOURCE_ID)
|
|
{
|
|
if (!createRenderResources(image, true))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
_imageBindingInfo[image->getResourceId()] = std::make_pair(image, samplingProperties);
|
|
return true;
|
|
}
|
|
|
|
rhi::ComPtr<rhi::ISampler> SlangTextureHandler::getSamplerState(const ImageSamplingProperties& samplingProperties)
|
|
{
|
|
auto samplerIt = _imageSamplerStateMap.find(samplingProperties);
|
|
if (samplerIt != _imageSamplerStateMap.end())
|
|
return samplerIt->second;
|
|
|
|
rhi::SamplerDesc samplerDesc = {};
|
|
samplerDesc.addressU = mapAddressModeToSlang(samplingProperties.uaddressMode);
|
|
samplerDesc.addressV = mapAddressModeToSlang(samplingProperties.vaddressMode);
|
|
samplerDesc.minFilter = mapFilterTypeToSlang(samplingProperties.filterType);
|
|
samplerDesc.magFilter = mapFilterTypeToSlang(samplingProperties.filterType);
|
|
samplerDesc.mipFilter = mapFilterTypeToSlang(samplingProperties.filterType);
|
|
if (!samplingProperties.enableMipmaps)
|
|
samplerDesc.maxLOD = 0.f;
|
|
|
|
// Border color not supported in Vulkan
|
|
samplerDesc.borderColor[0] = samplingProperties.defaultColor[0];
|
|
samplerDesc.borderColor[1] = samplingProperties.defaultColor[1];
|
|
samplerDesc.borderColor[2] = samplingProperties.defaultColor[2];
|
|
samplerDesc.borderColor[3] = samplingProperties.defaultColor[3];
|
|
if (samplingProperties.filterType != ImageSamplingProperties::FilterType::CLOSEST)
|
|
samplerDesc.maxAnisotropy = 16;
|
|
|
|
auto sampler = _device->createSampler(samplerDesc);
|
|
_imageSamplerStateMap[samplingProperties] = sampler;
|
|
|
|
return sampler;
|
|
}
|
|
|
|
bool SlangTextureHandler::bindImage(rhi::ShaderCursor cursor, ImagePtr image)
|
|
{
|
|
if (image->getResourceId() == SlangProgram::UNDEFINED_SLANG_RESOURCE_ID)
|
|
{
|
|
if (!createRenderResources(image, true))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
cursor["tex"].setBinding(_slangTextureMap[image->getResourceId()]);
|
|
cursor["sampler"].setBinding(getSamplerState(_imageBindingInfo[image->getResourceId()].second));
|
|
|
|
return true;
|
|
}
|
|
|
|
bool SlangTextureHandler::createRenderResources(ImagePtr image, bool generateMipMaps, bool useAsRenderTarget)
|
|
{
|
|
if (image->getResourceId() == SlangProgram::UNDEFINED_SLANG_RESOURCE_ID)
|
|
{
|
|
static unsigned int resourceId = 0;
|
|
image->setResourceId(++resourceId);
|
|
}
|
|
|
|
auto commandEncoder = _context->createCommandEncoder();
|
|
auto texture = makeTexture(commandEncoder, image.get(), generateMipMaps, useAsRenderTarget);
|
|
_context->submitCommandEncoder(commandEncoder);
|
|
_context->waitOnHost();
|
|
_slangTextureMap[image->getResourceId()] = std::move(texture);
|
|
|
|
return true;
|
|
}
|
|
|
|
/// Utility to map an address mode enumeration to an Slang RHI address mode
|
|
rhi::TextureAddressingMode SlangTextureHandler::mapAddressModeToSlang(ImageSamplingProperties::AddressMode addressModeEnum)
|
|
{
|
|
switch (addressModeEnum)
|
|
{
|
|
case ImageSamplingProperties::AddressMode::UNSPECIFIED:
|
|
return rhi::TextureAddressingMode::Wrap;
|
|
case ImageSamplingProperties::AddressMode::CONSTANT:
|
|
return rhi::TextureAddressingMode::ClampToBorder;
|
|
case ImageSamplingProperties::AddressMode::CLAMP:
|
|
return rhi::TextureAddressingMode::ClampToEdge;
|
|
case ImageSamplingProperties::AddressMode::PERIODIC:
|
|
return rhi::TextureAddressingMode::Wrap;
|
|
case ImageSamplingProperties::AddressMode::MIRROR:
|
|
return rhi::TextureAddressingMode::MirrorRepeat;
|
|
}
|
|
throw ExceptionRenderError("Unknown ImageSamplingProperties::AddressMode: " + std::to_string((int) addressModeEnum));
|
|
}
|
|
|
|
/// Utility to map a filter type enumeration to an Slang RHI filter type
|
|
rhi::TextureFilteringMode SlangTextureHandler::mapFilterTypeToSlang(ImageSamplingProperties::FilterType filterTypeEnum)
|
|
{
|
|
if (filterTypeEnum == ImageSamplingProperties::FilterType::CLOSEST)
|
|
return rhi::TextureFilteringMode::Point;
|
|
|
|
return rhi::TextureFilteringMode::Linear;
|
|
}
|
|
|
|
/// Utility to map generic texture properties to Slang RHI texture formats.
|
|
rhi::Format SlangTextureHandler::mapTextureFormatToSlang(Image::BaseType baseType, unsigned int channelCount, bool srgb)
|
|
{
|
|
return getRHIFormat(baseType, channelCount, srgb);
|
|
}
|
|
|
|
/// Makes sure the image has CPU data. If force is set to true, it will overwrite the data from GPU even if it exists.
|
|
ImagePtr SlangTextureHandler::uploadImageToCPU(ImagePtr image, bool force)
|
|
{
|
|
/// already have CPU data
|
|
if (image->getResourceBuffer() && !force)
|
|
return image;
|
|
|
|
SlangTexturePtr imageTexture = getAssociatedSlangTexture(image);
|
|
|
|
/// There is no associated GPU texture to upload
|
|
if (!imageTexture)
|
|
return {};
|
|
|
|
rhi::Format format = imageTexture->getDesc().format;
|
|
uint32_t width = imageTexture->getDesc().size.width;
|
|
uint32_t height = imageTexture->getDesc().size.height;
|
|
if (format != getRHIFormat(image->getBaseType(), image->getChannelCount()) ||
|
|
width != image->getWidth() || height != image->getHeight())
|
|
{
|
|
auto [channelCount, baseType] = getImageConfig(format);
|
|
if (channelCount == 0)
|
|
throw ExceptionRenderError("Incompatible image format");
|
|
image = Image::create(width, height, channelCount, baseType);
|
|
}
|
|
image->createResourceBuffer();
|
|
|
|
rhi::ComPtr<ISlangBlob> resultBlob;
|
|
rhi::SubresourceLayout textureLayout;
|
|
if (SLANG_FAILED(_device->readTexture(imageTexture.get(), 0, 0, resultBlob.writeRef(), &textureLayout)))
|
|
throw ExceptionRenderError("Failed to read texture data from GPU");
|
|
|
|
if (image->getRowStride() * image->getHeight() > textureLayout.sizeInBytes || image->getRowStride() > textureLayout.rowPitch)
|
|
throw ExceptionRenderError("Mismatched sizes");
|
|
|
|
if (image->getRowStride() != textureLayout.rowPitch)
|
|
{
|
|
auto dst = reinterpret_cast<uint8_t*>(image->getResourceBuffer());
|
|
auto src = reinterpret_cast<const uint8_t*>(resultBlob->getBufferPointer());
|
|
auto dstStride = image->getRowStride();
|
|
auto srcStride = textureLayout.rowPitch;
|
|
|
|
for (uint32_t y = 0; y < image->getHeight(); ++y)
|
|
memcpy(dst + y * dstStride, src + y * srcStride, dstStride);
|
|
}
|
|
else
|
|
{
|
|
memcpy(image->getResourceBuffer(), resultBlob->getBufferPointer(), image->getRowStride() * image->getHeight());
|
|
}
|
|
|
|
return image;
|
|
}
|
|
|
|
namespace
|
|
{
|
|
/// Returns true if the data can be directly uploaded to a texture
|
|
bool isCompatible(rhi::IDevice* device, Image::BaseType baseType, uint32_t channelCount)
|
|
{
|
|
if (channelCount == 1)
|
|
return false;
|
|
|
|
rhi::Format format = getRHIFormat(baseType, channelCount, false);
|
|
if (format == rhi::Format::Undefined)
|
|
return false;
|
|
|
|
rhi::FormatSupport support;
|
|
device->getFormatSupport(format, &support);
|
|
return is_set(support, rhi::FormatSupport::Texture) && is_set(support, rhi::FormatSupport::RenderTarget);
|
|
}
|
|
} // namespace
|
|
|
|
rhi::ComPtr<rhi::ITexture> SlangTextureHandler::makeTexture(SlangCommandEncoderPtr& commandEncoder, Image* image, bool generateMipMaps, bool useAsRenderTarget)
|
|
{
|
|
uint32_t channelCount = image->getChannelCount();
|
|
|
|
if (channelCount == 1)
|
|
channelCount = 3;
|
|
|
|
if (!isCompatible(_device, image->getBaseType(), channelCount))
|
|
{
|
|
channelCount = 4;
|
|
}
|
|
|
|
if (!isCompatible(_device, image->getBaseType(), channelCount))
|
|
{
|
|
throw ExceptionRenderError("Current image configuration cannot be made compatible with texture format");
|
|
}
|
|
|
|
rhi::TextureDesc textureDesc = {};
|
|
textureDesc.size.width = image->getWidth();
|
|
textureDesc.size.height = image->getHeight();
|
|
textureDesc.usage = rhi::TextureUsage::ShaderResource | rhi::TextureUsage::UnorderedAccess |
|
|
((useAsRenderTarget || generateMipMaps) ? rhi::TextureUsage::RenderTarget : rhi::TextureUsage::None);
|
|
textureDesc.defaultState = rhi::ResourceState::ShaderResource;
|
|
textureDesc.format = mapTextureFormatToSlang(image->getBaseType(), channelCount, false);
|
|
textureDesc.mipCount = generateMipMaps ? image->getMaxMipCount() : 1;
|
|
|
|
SlangTexturePtr texture = _device->createTexture(textureDesc);
|
|
|
|
/// If we can just upload the data, do that
|
|
if (channelCount == image->getChannelCount())
|
|
{
|
|
rhi::SubresourceRange subresourceRange;
|
|
subresourceRange.mip = 0;
|
|
subresourceRange.mipCount = 1;
|
|
subresourceRange.layer = 0;
|
|
subresourceRange.layerCount = 1;
|
|
|
|
rhi::SubresourceData subresourceData;
|
|
subresourceData.data = image->getResourceBuffer();
|
|
subresourceData.rowPitch = image->getRowStride();
|
|
subresourceData.slicePitch = 0;
|
|
|
|
commandEncoder->uploadTextureData(
|
|
texture,
|
|
subresourceRange,
|
|
rhi::Offset3D{ 0, 0, 0 },
|
|
rhi::Extent3D::kWholeTexture,
|
|
&subresourceData,
|
|
1);
|
|
}
|
|
else
|
|
{
|
|
/// Otherwise, we use compute shader to set the texture
|
|
rhi::BufferDesc bufferDesc = {};
|
|
bufferDesc.size = image->getWidth() * image->getHeight() * image->getChannelCount() * image->getBaseStride();
|
|
bufferDesc.usage = rhi::BufferUsage::ShaderResource | rhi::BufferUsage::UnorderedAccess | rhi::BufferUsage::CopyDestination;
|
|
|
|
auto srcBuffer = _device->createBuffer(bufferDesc, image->getResourceBuffer());
|
|
auto passEncoder = commandEncoder->beginComputePass();
|
|
auto rootObject = passEncoder->bindPipeline(getPipeline(image));
|
|
rhi::ShaderCursor cursor(rootObject->getEntryPoint(0));
|
|
|
|
cursor["dst"].setBinding(texture);
|
|
cursor["src"].setBinding(srcBuffer);
|
|
passEncoder->dispatchCompute(image->getWidth(), image->getHeight(), 1);
|
|
passEncoder->end();
|
|
}
|
|
|
|
if (generateMipMaps)
|
|
_context->_blitter->generateMips(commandEncoder, texture);
|
|
|
|
return texture;
|
|
}
|
|
|
|
rhi::ComPtr<rhi::IComputePipeline> SlangTextureHandler::getPipeline(Image* image)
|
|
{
|
|
return getPipeline(image->getChannelCount(), image->getBaseType());
|
|
}
|
|
|
|
rhi::ComPtr<rhi::IComputePipeline> SlangTextureHandler::getPipeline(unsigned channelCount, Image::BaseType baseType)
|
|
{
|
|
if (auto it = _pipelineCache.find({ channelCount, baseType }); it != _pipelineCache.end())
|
|
return it->second;
|
|
|
|
using namespace rhi;
|
|
|
|
ComPtr<slang::ISession> slangSession = _context->getDevice()->getSlangSession();
|
|
ComPtr<slang::IBlob> diagnosticsBlob;
|
|
std::vector<slang::IComponentType*> componentTypes;
|
|
ComPtr<slang::IEntryPoint> entryPoint;
|
|
ComPtr<slang::IComponentType> linkedProgram;
|
|
StringVec diagnosticVec;
|
|
|
|
auto validateResult = [&](bool success)
|
|
{
|
|
if (diagnosticsBlob)
|
|
diagnosticVec.push_back((const char*) diagnosticsBlob->getBufferPointer());
|
|
|
|
if (!success)
|
|
{
|
|
for (const auto& it : diagnosticVec)
|
|
std::cerr << it << std::endl;
|
|
throw ExceptionRenderError("Failed to compile blit shaders", diagnosticVec);
|
|
}
|
|
};
|
|
|
|
std::string source;
|
|
source += "#define SRC_SIZE " + std::to_string(channelCount) + "\n";
|
|
switch (baseType)
|
|
{
|
|
case Image::BaseType::UINT8:
|
|
source += "#define SRC_TYPE 0\n";
|
|
break;
|
|
case Image::BaseType::FLOAT:
|
|
source += "#define SRC_TYPE 1\n";
|
|
break;
|
|
default:
|
|
throw ExceptionRenderError("Cannot convert other formats yet.");
|
|
}
|
|
source += "#define SRC_SIZE " + std::to_string(channelCount);
|
|
source += CONVERSION_SHADER;
|
|
|
|
std::string name = _context->deduplicateName("conversion");
|
|
slang::IModule* module = slangSession->loadModuleFromSourceString(
|
|
name.c_str(), name.c_str(), source.c_str(), diagnosticsBlob.writeRef());
|
|
|
|
validateResult(module);
|
|
|
|
Result result;
|
|
|
|
componentTypes.push_back(module);
|
|
result = module->findEntryPointByName("copyData", entryPoint.writeRef());
|
|
validateResult(SLANG_SUCCEEDED(result));
|
|
componentTypes.push_back(entryPoint);
|
|
|
|
ComPtr<slang::IComponentType> composedProgram;
|
|
result = slangSession->createCompositeComponentType(
|
|
componentTypes.data(),
|
|
componentTypes.size(),
|
|
composedProgram.writeRef(),
|
|
diagnosticsBlob.writeRef());
|
|
validateResult(SLANG_SUCCEEDED(result));
|
|
|
|
result = composedProgram->link(linkedProgram.writeRef(), diagnosticsBlob.writeRef());
|
|
validateResult(SLANG_SUCCEEDED(result));
|
|
|
|
auto program = _context->getDevice()->createShaderProgram(linkedProgram, diagnosticsBlob.writeRef());
|
|
validateResult(program);
|
|
|
|
rhi::ComputePipelineDesc pipelineDesc = {};
|
|
pipelineDesc.program = program;
|
|
auto pipeline = _context->getDevice()->createComputePipeline(pipelineDesc);
|
|
|
|
_pipelineCache[{ channelCount, baseType }] = pipeline;
|
|
return pipeline;
|
|
}
|
|
|
|
const std::string SlangTextureHandler::CONVERSION_SHADER = R"===(
|
|
|
|
#define SRC_TYPE_UINT8 0
|
|
#define SRC_TYPE_FLOAT 1
|
|
|
|
#ifndef SRC_SIZE
|
|
#define SRC_SIZE 1
|
|
#endif
|
|
|
|
#ifndef SRC_TYPE
|
|
#define SRC_TYPE SRC_TYPE_FLOAT
|
|
#endif
|
|
|
|
#if SRC_TYPE == SRC_TYPE_UINT8
|
|
#define DST_TYPE float4
|
|
#else
|
|
#define DST_TYPE float4
|
|
#endif
|
|
|
|
uint4 loadUint8_tX(ByteAddressBuffer buffer, uint pixelIndex)
|
|
{
|
|
const uint size = SRC_SIZE;
|
|
uint byteIndex = pixelIndex * size;
|
|
uint alignedByteIndex = (byteIndex / 4) * 4;
|
|
/// This many bytes need to shift to align the values where we want them
|
|
uint32_t shift = byteIndex - alignedByteIndex;
|
|
|
|
/// Load into alignedValues, dealing with alignment issues (shift out previous data)
|
|
uint32_t alignedValues = buffer.Load(alignedByteIndex) >> (shift * 8);
|
|
if ((byteIndex - alignedByteIndex + size) > 4)
|
|
{
|
|
uint32_t temp = buffer.Load(alignedByteIndex + 4) << ((4 - shift) * 8);
|
|
alignedValues |= temp;
|
|
}
|
|
|
|
uint4 result = { 0, 0, 0, 255 };
|
|
for (uint i = 0; i < size; ++i)
|
|
{
|
|
result[i] = alignedValues & 255;
|
|
alignedValues >>= 8;
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
float4 loadFloat4(ByteAddressBuffer buffer, uint pixelIndex)
|
|
{
|
|
float4 result = { 0, 0, 0, 1 };
|
|
|
|
const uint size = SRC_SIZE;
|
|
uint byteIndex = pixelIndex * size * 4;
|
|
|
|
for (uint i = 0; i < size; ++i)
|
|
{
|
|
result[i] = buffer.Load<float>(byteIndex + 4 * i);
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
[shader("compute")]
|
|
[numthreads(16, 16, 1)]
|
|
void copyData(uint3 sv_dispatchThreadID: SV_DispatchThreadID,
|
|
uniform RWTexture2D<DST_TYPE> dst,
|
|
uniform ByteAddressBuffer src)
|
|
{
|
|
uint width;
|
|
uint height;
|
|
dst.GetDimensions(width, height);
|
|
|
|
if (sv_dispatchThreadID.x >= width || sv_dispatchThreadID.y >= height)
|
|
return;
|
|
|
|
uint pixelIndex = sv_dispatchThreadID.x + sv_dispatchThreadID.y * width;
|
|
#if SRC_TYPE == SRC_TYPE_FLOAT
|
|
DST_TYPE dstValue = loadFloat4(src, pixelIndex);
|
|
#elif SRC_TYPE == SRC_TYPE_UINT8
|
|
uint4 dstValue = loadUint8_tX(src, pixelIndex);
|
|
#endif
|
|
|
|
#if SRC_SIZE == 1
|
|
dstValue[1] = dstValue[0];
|
|
dstValue[2] = dstValue[0];
|
|
#endif
|
|
|
|
#if SRC_TYPE == SRC_TYPE_FLOAT
|
|
dst[sv_dispatchThreadID.xy] = dstValue;
|
|
#elif SRC_TYPE == SRC_TYPE_UINT8
|
|
dst[sv_dispatchThreadID.xy] = dstValue / 255.0;
|
|
#endif
|
|
}
|
|
|
|
)===";
|
|
|
|
MATERIALX_NAMESPACE_END
|