cnq3/code/renderer/srp_mip_gen.cpp

349 lines
9.3 KiB
C++

/*
===========================================================================
Copyright (C) 2023-2024 Gian 'myT' Schellenbaum
This file is part of Challenge Quake 3 (CNQ3).
Challenge Quake 3 is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
Challenge Quake 3 is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Challenge Quake 3. If not, see <https://www.gnu.org/licenses/>.
===========================================================================
*/
// Shared Rendering Pipeline - texture mip-map generation
#include "srp_local.h"
#pragma pack(push, 4)
struct StartConstants
{
float gamma;
uint32_t srcTexture;
uint32_t dstTexture;
};
struct DownConstants
{
float weights[4];
int32_t maxSize[2];
int32_t scale[2];
int32_t offset[2];
uint32_t clampMode; // 0 = repeat
uint32_t srcMip;
uint32_t dstMip;
uint32_t srcTexture;
uint32_t dstTexture;
};
struct EndConstants
{
float blendColor[4];
float intensity;
float invGamma; // 1.0 / gamma
uint32_t srcMip;
uint32_t dstMip;
uint32_t srcTexture;
uint32_t dstTexture;
};
#pragma pack(pop)
void MipMapGenerator::Init(bool ddhi_, const ShaderByteCode& g2l, const ShaderByteCode& down, const ShaderByteCode& l2g)
{
if(!srp.firstInit)
{
return;
}
ddhi = ddhi_;
for(int t = 0; t < 2; ++t)
{
TextureDesc desc(va("mip-map generation #%d", t + 1), MAX_TEXTURE_SIZE, MAX_TEXTURE_SIZE);
desc.format = TextureFormat::RGBA64_Float;
desc.initialState = ResourceStates::UnorderedAccessBit;
desc.allowedState = ResourceStates::UnorderedAccessBit | ResourceStates::ComputeShaderAccessBit;
textures[MipSlice::Float16_0 + t] = CreateTexture(desc);
}
const char* const stageNames[] = { "start", "down", "end" };
const uint32_t stageRCByteCount[] = { sizeof(StartConstants), sizeof(DownConstants), sizeof(EndConstants) };
const uint32_t stageExtraMips[] = { 1, 0, MaxTextureMips };
const ShaderByteCode stageShaders[] = { g2l, down, l2g };
for(int s = 0; s < 3; ++s)
{
Stage& stage = stages[s];
if(ddhi)
{
stage.rootSignature = RHI_MAKE_NULL_HANDLE();
}
else
{
{
RootSignatureDesc desc(va("mip-map %s", stageNames[s]));
desc.pipelineType = PipelineType::Compute;
desc.constants[ShaderStage::Compute].byteCount = stageRCByteCount[s];
desc.AddRange(DescriptorType::RWTexture, 0, MipSlice::Count + stageExtraMips[s]);
stage.rootSignature = CreateRootSignature(desc);
}
{
const DescriptorTableDesc desc(DescriptorTableDesc(va("mip-map %s", stageNames[s]), stage.rootSignature));
stage.descriptorTable = CreateDescriptorTable(desc);
DescriptorTableUpdate update;
update.SetRWTexturesSlice(ARRAY_LEN(textures), textures, 0, 0);
UpdateDescriptorTable(stage.descriptorTable, update);
}
}
{
ComputePipelineDesc desc(va("mip-map %s", stageNames[s]), stage.rootSignature);
desc.shader = stageShaders[s];
stage.pipeline = CreateComputePipeline(desc);
}
}
}
void MipMapGenerator::GenerateMipMaps(HTexture texture)
{
// @TODO: better look-up
image_t* image = NULL;
for(int i = 0; i < tr.numImages; ++i)
{
if(tr.images[i]->texture == texture)
{
image = tr.images[i];
break;
}
}
Q_assert(image);
if(image == NULL || (image->flags & IMG_NOMIPMAP) != 0)
{
return;
}
const int mipCount = R_ComputeMipCount(image->width, image->height);
if(mipCount <= 1)
{
return;
}
BeginTempCommandList();
{
TextureBarrier allBarriers[MipSlice::Count + 1];
for(int i = 0; i < MipSlice::Count; ++i)
{
allBarriers[i] = TextureBarrier(textures[i], ResourceStates::UnorderedAccessBit);
}
allBarriers[MipSlice::Count] = TextureBarrier(texture, ResourceStates::UnorderedAccessBit);
CmdBarrier(ARRAY_LEN(allBarriers), allBarriers);
}
if(!ddhi)
{
// this must happen after the BeginTempCommandList call because
// it has a CPU wait that guarantees it's safe to update the descriptor tables
{
Stage& stage = stages[Stage::Start];
DescriptorTableUpdate update;
update.SetRWTexturesSlice(1, &texture, MipSlice::Count, 0);
UpdateDescriptorTable(stage.descriptorTable, update);
}
{
Stage& stage = stages[Stage::End];
DescriptorTableUpdate update;
update.SetRWTexturesChain(1, &texture, MipSlice::Count);
UpdateDescriptorTable(stage.descriptorTable, update);
}
}
int w = image->width;
int h = image->height;
enum { GroupSize = 8, GroupMask = GroupSize - 1 };
#define Dispatch(Width, Height) CmdDispatch((Width + GroupMask) / GroupSize, (Height + GroupMask) / GroupSize, 1)
// create a linear-space copy of mip 0 into float16 texture 0
{
Stage& stage = stages[Stage::Start];
StartConstants rc = {};
rc.gamma = r_mipGenGamma->value;
if(!ddhi)
{
CmdBindRootSignature(stage.rootSignature);
}
CmdBindPipeline(stage.pipeline);
if(ddhi)
{
rc.srcTexture = GetTextureIndexUAV(texture, 0);
rc.dstTexture = GetTextureIndexUAV(textures[MipSlice::Float16_0], 0);
CmdSetComputeRootConstants(0, sizeof(rc), &rc);
}
else
{
CmdBindDescriptorTable(stage.rootSignature, stage.descriptorTable);
CmdSetRootConstants(stage.rootSignature, ShaderStage::Compute, &rc);
}
Dispatch(w, h);
}
TextureBarrier tempBarriers[MipSlice::Count];
for(int i = 0; i < MipSlice::Count; ++i)
{
tempBarriers[i] = TextureBarrier(textures[i], ResourceStates::UnorderedAccessBit);
}
// overwrite mip 0 to apply r_intensity if needed
if((image->flags & IMG_NOIMANIP) == 0 &&
r_intensity->value != 1.0f)
{
Stage& stage = stages[Stage::End];
const int destMip = 0;
EndConstants rc = {};
rc.intensity = r_intensity->value;
rc.invGamma = 1.0f / r_mipGenGamma->value;
Vector4Clear(rc.blendColor);
rc.srcMip = MipSlice::Float16_0;
rc.dstMip = MipSlice::Count + destMip;
if(!ddhi)
{
CmdBindRootSignature(stage.rootSignature);
}
CmdBindPipeline(stage.pipeline);
if(ddhi)
{
rc.srcTexture = GetTextureIndexUAV(textures[MipSlice::Float16_0], 0);
rc.dstTexture = GetTextureIndexUAV(texture, destMip);
CmdSetComputeRootConstants(0, sizeof(rc), &rc);
}
else
{
CmdBindDescriptorTable(stage.rootSignature, stage.descriptorTable);
CmdSetRootConstants(stage.rootSignature, ShaderStage::Compute, &rc);
}
CmdBarrier(ARRAY_LEN(tempBarriers), tempBarriers);
Dispatch(w, h);
}
for(int i = 1; i < mipCount; ++i)
{
const int ws = w;
const int hs = h;
w = max(w / 2, 1);
h = max(h / 2, 1);
// down-sample the image into float16 texture 1 and then 0
{
Stage& stage = stages[Stage::DownSample];
DownConstants rc = {};
rc.clampMode = image->wrapClampMode == TW_REPEAT ? 0 : 1;
memcpy(rc.weights, tr.mipFilter, sizeof(rc.weights));
if(!ddhi)
{
CmdBindRootSignature(stage.rootSignature);
}
CmdBindPipeline(stage.pipeline);
if(!ddhi)
{
CmdBindDescriptorTable(stage.rootSignature, stage.descriptorTable);
}
// down-sample on the X-axis
rc.srcMip = MipSlice::Float16_0;
rc.dstMip = MipSlice::Float16_1;
rc.scale[0] = ws / w;
rc.scale[1] = 1;
rc.maxSize[0] = ws - 1;
rc.maxSize[1] = hs - 1;
rc.offset[0] = 1;
rc.offset[1] = 0;
if(ddhi)
{
rc.srcTexture = GetTextureIndexUAV(textures[MipSlice::Float16_0], 0);
rc.dstTexture = GetTextureIndexUAV(textures[MipSlice::Float16_1], 0);
CmdSetComputeRootConstants(0, sizeof(rc), &rc);
}
else
{
CmdSetRootConstants(stage.rootSignature, ShaderStage::Compute, &rc);
}
CmdBarrier(ARRAY_LEN(tempBarriers), tempBarriers);
Dispatch(w, hs);
// down-sample on the Y-axis
rc.srcMip = MipSlice::Float16_1;
rc.dstMip = MipSlice::Float16_0;
rc.scale[0] = 1;
rc.scale[1] = hs / h;
rc.maxSize[0] = w - 1;
rc.maxSize[1] = hs - 1;
rc.offset[0] = 0;
rc.offset[1] = 1;
if(ddhi)
{
rc.srcTexture = GetTextureIndexUAV(textures[MipSlice::Float16_1], 0);
rc.dstTexture = GetTextureIndexUAV(textures[MipSlice::Float16_0], 0);
CmdSetComputeRootConstants(0, sizeof(rc), &rc);
}
else
{
CmdSetRootConstants(stage.rootSignature, ShaderStage::Compute, &rc);
}
CmdBarrier(ARRAY_LEN(tempBarriers), tempBarriers);
Dispatch(w, h);
}
// save the results in gamma-space
{
Stage& stage = stages[Stage::End];
const int destMip = i;
EndConstants rc = {};
rc.intensity = (image->flags & IMG_NOIMANIP) ? 1.0f : r_intensity->value;
rc.invGamma = 1.0f / r_mipGenGamma->value;
memcpy(rc.blendColor, r_mipBlendColors[r_colorMipLevels->integer ? destMip : 0], sizeof(rc.blendColor));
rc.srcMip = MipSlice::Float16_0;
rc.dstMip = MipSlice::Count + destMip;
if(!ddhi)
{
CmdBindRootSignature(stage.rootSignature);
}
CmdBindPipeline(stage.pipeline);
if(ddhi)
{
rc.srcTexture = GetTextureIndexUAV(textures[MipSlice::Float16_0], 0);
rc.dstTexture = GetTextureIndexUAV(texture, destMip);
CmdSetComputeRootConstants(0, sizeof(rc), &rc);
}
else
{
CmdBindDescriptorTable(stage.rootSignature, stage.descriptorTable);
CmdSetRootConstants(stage.rootSignature, ShaderStage::Compute, &rc);
}
CmdBarrier(ARRAY_LEN(tempBarriers), tempBarriers);
Dispatch(w, h);
}
}
#undef Dispatch
EndTempCommandList();
}