due to restrictions in some apis I can't upload 3D textures onto the GPU, that's why I thought to compress the 3D texture data into an wider 2D texture and upload the 2D texture instead with different UV coordinates. The problem is, that the resulting texture on the gpu has only very thin slices of the texture and the rest of the texture is just white. I would have expected the texture to look different, and if I apply the texture to the output image I look like if I was made out of metal (the texture to upload is a 3D lut image and I apply the lut in the shader to the camera feed).
The textures are always quadratic, here is my code to compress the texture data on the CPU:
size_t Compress3DTextureInto2DTexture(
const uint8_t* in3DBuffer, size_t in3DBufferSize, uint32_t inWidth, uint32_t inHeight, uint32_t inDepth, uint8_t** out2DBuffer)
{
uint32_t texture2DWidth = inWidth * inDepth;
uint32_t texture2DHeight = inHeight;
size_t buffer2DSize = texture2DWidth * texture2DHeight * sizeof(uint8_t);
uint8_t *buffer = new uint8_t[buffer2DSize];
for (int z = 0; z < inDepth; ++z)
{
for (int y = 0; y < inHeight; ++y)
{
for (int x = 0; x < inWidth; ++x)
{
int index3D = x + y * inWidth + z * inWidth * inHeight;
int index2D = x + z * inWidth + y * texture2DWidth;
// Make sure that the buffers can never be overrun.
if (index3D < in3DBufferSize && index2D < buffer2DSize)
buffer[index2D] = in3DBuffer[index3D];
}
}
}
*out2DBuffer = buffer;
return buffer2DSize;
}
and then the data is normally uploaded using DirectX11 and a bitmap is created as well for the effect api of Direct2D:
CComPtr<ID2D1Bitmap1> d2dBitmap = nullptr;
CD3D11_TEXTURE2D_DESC1 textureDesc;
textureDesc.Format = DxgiFormatFromGPUBufferType(inConfig.mBufferType);
textureDesc.Width = inConfig.mWidth;
textureDesc.Height = inConfig.mHeight;
textureDesc.ArraySize = 1;
textureDesc.MipLevels = 1;
textureDesc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE;
textureDesc.Usage = D3D11_USAGE_DEFAULT;
textureDesc.CPUAccessFlags = 0;
textureDesc.SampleDesc.Count = 1;
textureDesc.SampleDesc.Quality = 0;
textureDesc.TextureLayout = D3D11_TEXTURE_LAYOUT_UNDEFINED;
textureDesc.MiscFlags = D3D11_RESOURCE_MISC_SHARED;
ID3D11Texture2D1* d3d11Texture = nullptr;
D3D11_SUBRESOURCE_DATA initialData;
initialData.pSysMem = inConfig.mSrcData;
initialData.SysMemPitch = inConfig.mWidth << 2;
initialData.SysMemSlicePitch = initialData.SysMemPitch * inConfig.mHeight;
HRESULT hr = inDevice->CreateTexture2D1(&textureDesc, inConfig.mSrcData ? &initialData : nullptr, &d3d11Texture);
if (SUCCEEDED(hr) && d3d11Texture)
{
IDXGISurface* dxgiSurface = nullptr;
hr = d3d11Texture->QueryInterface(&dxgiSurface);
if (SUCCEEDED(hr) && dxgiSurface)
{
hr = inDeviceContext->CreateBitmapFromDxgiSurface(dxgiSurface, &inBitmapProperties, &d2dBitmap);
if (FAILED(hr) || !d2dBitmap)
{
ASSERT(false);
}
dxgiSurface->Release();
}
else
{
ASSERT(false);
}
d3d11Texture->Release();
}
else
{
ASSERT(false);
}
*outBitmap = d2dBitmap;
return hr;
Here is the usage of the effect api from Direct2D (executed on every frame as the update logic):
D2D_VECTOR_3F domainMin = utils::Vec3ToD2DVec3(mParams.DomainMin);
D2D_VECTOR_3F domainMax = utils::Vec3ToD2DVec3(mParams.DomainMax);
D2D_VECTOR_3F lutOffset = utils::Vec3ToD2DVec3(mParams.LutOffset);
D2D_VECTOR_3F lutScale = utils::Vec3ToD2DVec3(mParams.LutScale);
// Apply shader effect for LUT effect
inD2DContext->SetTarget(inTargetBmp);
inD2DContext->BeginDraw();
inD2DContext->Clear(D2D1::ColorF(D2D1::ColorF::Black, 0.f));
// The camera feed
mEffect->SetInput(TextureBinding::InputTexture, inSourceBmp);
// Because both different texture types are compressed into a 2D texture,
// we can set the input texture always as a 2D texture.
mEffect->SetInput(TextureBinding::LUTTexture2D, mTexture->GetData()->GetD2dBitmap());
VALIDATE_EFFECT_PARAM(mEffect->SetValue(LUTEFFECT_PROP_LUT_MODE, (int)(mLUTType == EGAVLutType::Lut3D)));
VALIDATE_EFFECT_PARAM(mEffect->SetValue(LUTEFFECT_PROP_INTENSITY, mParams.Intensity));
VALIDATE_EFFECT_PARAM(mEffect->SetValue(LUTEFFECT_PROP_WIDTH, mParams.LutWidth));
VALIDATE_EFFECT_PARAM(mEffect->SetValue(LUTEFFECT_PROP_DOMAIN_MIN, &domainMin));
VALIDATE_EFFECT_PARAM(mEffect->SetValue(LUTEFFECT_PROP_DOMAIN_MAX, &domainMax));
VALIDATE_EFFECT_PARAM(mEffect->SetValue(LUTEFFECT_PROP_OFFSET, &lutOffset));
VALIDATE_EFFECT_PARAM(mEffect->SetValue(LUTEFFECT_PROP_SCALE, &lutScale));
// draw the actual image
inD2DContext->DrawImage(mEffect);
HRESULT hr = inD2DContext->EndDraw();
if (SUCCEEDED(hr))
{
res = EGAVResult::Ok;
}
And finally the shader where the lut effect is applied:
Texture2D InputTexture : register(t0);
Texture2D LUTTexture : register(t1);
SamplerState InputSampler : register(s0);
SamplerState LUTSampler : register(s1)
{
Filter = Linear;
AddressU = CLAMP;
AddressV = CLAMP;
AddressW = CLAMP;
};
cbuffer constants : register(b0)
{
float Intensity : packoffset(c0.x);
uint LutMode : packoffset(c0.y);
float Width : packoffset(c0.z);
float Padding1 : packoffset(c0.w);
float3 DomainMin : packoffset(c1.x);
float DomainMinPadding : packoffset(c1.w); // Padding to align to 16 bytes
float3 DomainMax : packoffset(c2.x);
float DomainMaxPadding : packoffset(c2.w); // Padding to align to 16 bytes
float3 Scale : packoffset(c3.x); // This assumes that LutMode and Amount pack into the first 8 bytes of c3
float ScalePadding : packoffset(c3.w); // Padding to align to 16 bytes
float3 Offset : packoffset(c4.x);
float OffsetPadding : packoffset(c4.w); // Padding to align to 16 bytes
};
float srgb_linear_to_nonlinear_channel(float u)
{
return (u <= 0.0031308) ? (12.92 * u) : ((1.055 * pow(u, 1.0 / 2.4)) - 0.055);
}
float3 srgb_linear_to_nonlinear(float3 v)
{
return float3(srgb_linear_to_nonlinear_channel(v.r),
srgb_linear_to_nonlinear_channel(v.g), srgb_linear_to_nonlinear_channel(v.b));
}
// Here the uploaded 2D texture is interpreted back as the 3D texture by adjusting the uvw coords
float4 sample_3d_lut(float4 inputTexture, float3 texCoord)
{
int sliceIndex = int(texCoord.z * Width);
float2 packedTexCoord = float2((sliceIndex * Width + texCoord.x) / Width, texCoord.y);
return LUTTexture.Sample(InputSampler, packedTexCoord);
}
float4 apply_lut_3d(float4 input_texture, float3 lut_scale, float3 lut_offset, float lut_amount, float3 domain_min, float3 domain_max, bool apply_input_alpha, bool apply_domain)
{
float4 result = input_texture;
if (apply_input_alpha || apply_domain)
{
// Remove the alpha influence
result.rgb = max(float3(0.0, 0.0, 0.0), input_texture.rgb / input_texture.a);
}
float3 non_linear = srgb_linear_to_nonlinear(result.rgb);
if (apply_domain)
{
float r = non_linear.r;
float g = non_linear.g;
float b = non_linear.b;
if (r >= domain_min.r && r <= domain_max.r
&& g >= domain_min.g && g <= domain_max.g
&& b >= domain_min.b && b <= domain_max.b)
{
float3 uvw = non_linear * lut_scale + lut_offset;
float3 lut_color = sample_3d_lut(result, uvw).rgb;
result.rgb = lerp(result.rgb, lut_color, lut_amount);
}
result.rgb *= result.a;
return result;
}
float3 uvw = non_linear * lut_scale + lut_offset;
float3 lut_color = sample_3d_lut(result, uvw).rgb;
result.rgb = lut_color;
if (apply_input_alpha)
{
result.rgb = lerp(result.rgb, lut_color, lut_amount);
result.rgb *= input_texture.a;
}
return result;
}
float4 main(
float4 pos : SV_POSITION,
float4 posScene : SCENE_POSITION,
float3 uv0 : TEXCOORD0
) : SV_Target
{
float4 color = InputTexture.Sample(InputSampler, uv0.xy);
const float amount = Intensity / 100.0;
if (0 == LutMode) // 1D LUT
{
return apply_lut_1d(color, amount);
}
else if (1 == LutMode) // 3D LUT
{
return apply_lut_3d(color, Scale, Offset, amount, DomainMin, DomainMax, false, false);
}
return color;
}
I would be very thankful if anyone could direct me into the right direction, I tried to get it working for several weeks now, but didn't get it working yet.