Torque3D/Engine/source/shaderGen/shaderGen.cpp
marauder2k7 4cf780e7b8 Shader Gen to produce ShaderData
Shader gen now produces a shaderdata class - this should reduce full recompilation of shaders when a macro switch happens.

FileStream can now also be setup to be async write, so it will write out the file on a separate thread hopefully freeing up the main thread to continue working.
2026-06-05 11:29:18 +01:00

609 lines
17 KiB
C++

//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "shaderGen/shaderGen.h"
#include "shaderGen/conditionerFeature.h"
#include "core/stream/fileStream.h"
#include "shaderGen/featureMgr.h"
#include "shaderGen/shaderOp.h"
#include "gfx/gfxDevice.h"
#include "core/memVolume.h"
#include "core/module.h"
#include "console/persistenceManager.h"
#ifdef TORQUE_D3D11
#include "shaderGen/HLSL/customFeatureHLSL.h"
#endif
#ifdef TORQUE_OPENGL
#include "shaderGen/GLSL/customFeatureGLSL.h"
#endif
MODULE_BEGIN( ShaderGen )
MODULE_INIT_BEFORE( GFX )
MODULE_SHUTDOWN_AFTER( GFX )
MODULE_INIT
{
ManagedSingleton< ShaderGen >::createSingleton();
}
MODULE_SHUTDOWN
{
ManagedSingleton< ShaderGen >::deleteSingleton();
}
MODULE_END;
static const U32 gStageOrder[] =
{
GFXShaderStage::VERTEX_SHADER,
GFXShaderStage::HULL_SHADER,
GFXShaderStage::DOMAIN_SHADER,
GFXShaderStage::GEOMETRY_SHADER,
GFXShaderStage::PIXEL_SHADER,
GFXShaderStage::COMPUTE_SHADER
};
static const char* _getStagePostfix(GFXShaderStage stage)
{
switch (stage)
{
case GFXShaderStage::VERTEX_SHADER: return "_V";
case GFXShaderStage::HULL_SHADER: return "_H";
case GFXShaderStage::DOMAIN_SHADER: return "_D";
case GFXShaderStage::GEOMETRY_SHADER: return "_G";
case GFXShaderStage::PIXEL_SHADER: return "_P";
case GFXShaderStage::COMPUTE_SHADER: return "_C";
}
return "_U"; // Unknown
}
String ShaderGen::smCommonShaderPath("shaders/common");
ShaderGen::ShaderGen()
{
mInit = false;
GFXDevice::getDeviceEventSignal().notify(this, &ShaderGen::_handleGFXEvent);
mOutput = NULL;
}
ShaderGen::~ShaderGen()
{
GFXDevice::getDeviceEventSignal().remove(this, &ShaderGen::_handleGFXEvent);
_uninit();
for (ShaderDataMap::Pair data : mProcShaderData)
{
if (data.value->isProperlyAdded() && !data.value->isDeleted())
data.value->unregisterObject();
}
mProcShaderData.clear();
}
void ShaderGen::registerInitDelegate(GFXAdapterType adapterType, ShaderGenInitDelegate& initDelegate)
{
mInitDelegates[(U32)adapterType] = initDelegate;
}
bool ShaderGen::_handleGFXEvent(GFXDevice::GFXDeviceEventType event)
{
switch (event)
{
case GFXDevice::deInit :
initShaderGen();
break;
case GFXDevice::deDestroy :
{
flushProceduralShaders();
}
break;
default :
break;
}
return true;
}
void ShaderGen::initShaderGen()
{
if (mInit)
return;
const GFXAdapterType adapterType = GFX->getAdapterType();
const bool isGl = adapterType == GFXAdapterType::OpenGL;
if (!mInitDelegates[adapterType])
return;
smCommonShaderPath = String(Con::getVariable("$Core::CommonShaderPath", "shaders/common"));
mInitDelegates[adapterType](this);
mFeatureInitSignal.trigger( adapterType );
mInit = true;
String shaderPath = Con::getVariable( "$shaderGen::cachePath");
if (!shaderPath.equal( "shadergen:" ) && !shaderPath.isEmpty() )
{
// this is necessary, especially under Windows with UAC enabled
if (!Torque::FS::VerifyWriteAccess(shaderPath))
{
// we don't have write access so enable the virtualized memory store
Con::warnf("ShaderGen: Write permission unavailable, switching to virtualized memory storage");
shaderPath.clear();
}
}
if ( shaderPath.equal( "shadergen:" ) || shaderPath.isEmpty() )
{
// If we didn't get a path then we're gonna cache the shaders to
// a virtualized memory file system.
mMemFS = new Torque::Mem::MemFileSystem( "shadergen:/" );
Torque::FS::Mount( "shadergen", mMemFS );
}
else
Torque::FS::Mount( "shadergen", shaderPath + "/" );
// Delete the auto-generated conditioner include file.
Torque::FS::Remove( "shadergen:/" + ConditionerFeature::ConditionerIncludeFileName );
Vector<String> fileList;
String pattern = "*.";
pattern += isGl ? "glsl" : "hlsl";
S32 numShaderFiles = Torque::FS::FindByPattern("shadergen:/", pattern, false, fileList);
for (U32 i = 0; i < numShaderFiles; i++)
{
Torque::Path filePath = fileList[i];
mFileCache[filePath.getFileName()] = true;
}
}
void ShaderGen::generateShader(const MaterialFeatureData& featureData,
ShaderData* shaderData,
const GFXVertexFormat* vertexFormat,
const char* cacheName,
Vector<GFXShaderMacro>& macros)
{
PROFILE_SCOPE(ShaderGen_GenerateShader);
mFeatureData = featureData;
mVertexFormat = vertexFormat;
_uninit();
_init();
const FeatureSet& features = mFeatureData.features;
U32 stages = 0;
// loop through and see which stages this featureset is expecting to make.
for (U32 i = 0; i < features.getCount(); i++)
{
const FeatureType& type = features.getAt(i);
ShaderFeature* feat = FEATUREMGR->getByType(type);
stages |= feat->getShaderStages();
}
for (U32 s = 0; s < (sizeof(gStageOrder) / sizeof(U32)); s++)
{
U32 stage = gStageOrder[s];
// skip unused stages
if (!(stages & stage))
continue;
bool macrosOnly = !Con::getBoolVariable("ShaderGen::GenNewShaders", true);
bool skipPrint = false;
GFXShaderStage curStage = (GFXShaderStage)stage;
char fileName[256];
const char* postfix = _getStagePostfix(curStage);
String stageName;
if (curStage & GFXShaderStage::VERTEX_SHADER)
stageName += vertexFormat->getDescription();
// build our filename.
for (U32 i = 0; i < features.getCount(); i++)
{
const FeatureType& type = features.getAt(i);
if (stage & FEATUREMGR->getByType(type)->getShaderStages())
{
stageName += type.getName();
}
}
stageName = Torque::getStringHash64(stageName);
stageName += postfix;
FileCacheSet::iterator file = mFileCache.find(stageName);
if (file != mFileCache.end())
{
// set the shaderdata file for this stage, shaderdata ptr needs to be passed in here.
dSprintf(fileName, sizeof(fileName), "shadergen:/%s.%s", stageName.c_str(), mFileEnding.c_str());
shaderData->setShaderStageFile(curStage, fileName);
if (!(curStage & GFXShaderStage::VERTEX_SHADER))
{
continue;
}
skipPrint = true;
}
mFileCache[stageName] = true;
dSprintf(fileName, sizeof(fileName), "shadergen:/%s.%s", stageName.c_str(), mFileEnding.c_str());
shaderData->setShaderStageFile(curStage, fileName);
FileStream* stream = new FileStream(FileStream::AsyncMode::Background);
if (!skipPrint)
{
if (!stream->open(fileName, Torque::FS::File::Write))
{
AssertFatal(false, "Failed to open Shader Stream");
return;
}
}
switch (curStage)
{
case VERTEX_SHADER:
mOutput = new MultiLine;
mInstancingFormat.clear();
_processVertFeatures(macros, macrosOnly);
if (!skipPrint || macrosOnly) _printVertShader(*stream);
delete stream;
((ShaderConnector*)mComponents[C_CONNECTOR])->reset();
LangElement::deleteElements();
break;
case PIXEL_SHADER:
mOutput = new MultiLine;
_processPixFeatures(macros, macrosOnly);
if (!skipPrint || macrosOnly)_printPixShader(*stream);
delete stream;
LangElement::deleteElements();
break;
case GEOMETRY_SHADER:
break;
case DOMAIN_SHADER:
break;
case HULL_SHADER:
break;
case COMPUTE_SHADER:
break;
case ALL_STAGES:
break;
default:
break;
}
}
}
void ShaderGen::_init()
{
_createComponents();
}
void ShaderGen::_uninit()
{
for( U32 i=0; i<mComponents.size(); i++ )
{
delete mComponents[i];
mComponents[i] = NULL;
}
mComponents.setSize(0);
LangElement::deleteElements();
Var::reset();
}
void ShaderGen::_createComponents()
{
ShaderComponent* vertComp = mComponentFactory->createVertexInputConnector( *mVertexFormat );
mComponents.push_back(vertComp);
ShaderComponent* vertPixelCon = mComponentFactory->createVertexPixelConnector();
mComponents.push_back(vertPixelCon);
ShaderComponent* vertParamDef = mComponentFactory->createVertexParamsDef();
mComponents.push_back(vertParamDef);
ShaderComponent* pixParamDef = mComponentFactory->createPixelParamsDef();
mComponents.push_back(pixParamDef);
}
//----------------------------------------------------------------------------
// Process features
//----------------------------------------------------------------------------
void ShaderGen::_processVertFeatures( Vector<GFXShaderMacro> &macros, bool macrosOnly )
{
const FeatureSet &features = mFeatureData.features;
for( U32 i=0; i < features.getCount(); i++ )
{
S32 index;
const FeatureType &type = features.getAt( i, &index );
void* args = features.getArguments(i);
ShaderFeature* feature = NULL;
if(args)
feature = FEATUREMGR->createFeature(type, args);
else
feature = FEATUREMGR->getByType( type );
if ( feature && (feature->getShaderStages() & GFXShaderStage::VERTEX_SHADER) )
{
feature->setProcessIndex( index );
feature->processVertMacros( macros, mFeatureData );
if ( macrosOnly )
continue;
feature->setInstancingFormat( &mInstancingFormat );
feature->mVertexFormat = mVertexFormat;
feature->processVert( mComponents, mFeatureData );
String line;
if ( index > -1 )
line = String::ToString( " // %s %d\r\n", feature->getName().c_str(), index );
else
line = String::ToString( " // %s\r\n", feature->getName().c_str() );
mOutput->addStatement( new GenOp( line ) );
if ( feature->getOutput() )
mOutput->addStatement( feature->getOutput() );
feature->reset();
mOutput->addStatement( new GenOp( " \r\n" ) );
}
}
ShaderConnector *connect = dynamic_cast<ShaderConnector *>( mComponents[C_CONNECTOR] );
connect->sortVars();
}
void ShaderGen::_processPixFeatures( Vector<GFXShaderMacro> &macros, bool macrosOnly )
{
const FeatureSet &features = mFeatureData.features;
for( U32 i=0; i < features.getCount(); i++ )
{
S32 index;
const FeatureType &type = features.getAt( i, &index );
void* args = features.getArguments(i);
ShaderFeature* feature = NULL;
if (args)
feature = FEATUREMGR->createFeature(type, args);
else
feature = FEATUREMGR->getByType(type);
if ( feature && (feature->getShaderStages() & GFXShaderStage::PIXEL_SHADER) )
{
feature->setProcessIndex( index );
feature->processPixMacros( macros, mFeatureData );
if ( macrosOnly )
continue;
feature->setInstancingFormat( &mInstancingFormat );
feature->processPix( mComponents, mFeatureData );
String line;
if ( index > -1 )
line = String::ToString( " // %s %d\r\n", feature->getName().c_str(), index );
else
line = String::ToString( " // %s\r\n", feature->getName().c_str() );
mOutput->addStatement( new GenOp( line ) );
if ( feature->getOutput() )
mOutput->addStatement( feature->getOutput() );
feature->reset();
mOutput->addStatement( new GenOp( " \r\n" ) );
}
}
ShaderConnector *connect = dynamic_cast<ShaderConnector *>( mComponents[C_CONNECTOR] );
connect->sortVars();
}
void ShaderGen::_printFeatureList(Stream &stream)
{
mPrinter->printLine(stream, "// Features:");
const FeatureSet &features = mFeatureData.features;
for( U32 i=0; i < features.getCount(); i++ )
{
S32 index;
const FeatureType &type = features.getAt( i, &index );
void* args = features.getArguments(i);
ShaderFeature* feature = NULL;
if (args)
feature = FEATUREMGR->createFeature(type, args);
else
feature = FEATUREMGR->getByType(type);
if ( feature )
{
String line;
if ( index > -1 )
line = String::ToString( "// %s %d", feature->getName().c_str(), index );
else
line = String::ToString( "// %s", feature->getName().c_str() );
mPrinter->printLine( stream, line );
}
}
mPrinter->printLine(stream, "");
}
void ShaderGen::_printDependencies(Stream &stream)
{
Vector<const ShaderDependency *> dependencies;
for( U32 i=0; i < FEATUREMGR->getFeatureCount(); i++ )
{
const FeatureInfo &info = FEATUREMGR->getAt( i );
if ( mFeatureData.features.hasFeature( *info.type ) )
dependencies.merge( info.feature->getDependencies() );
}
// Do a quick loop removing any duplicate dependancies.
for( U32 i=0; i < dependencies.size(); )
{
bool dup = false;
for( U32 j=0; j < dependencies.size(); j++ )
{
if ( j != i &&
*dependencies[i] == *dependencies[j] )
{
dup = true;
break;
}
}
if ( dup )
dependencies.erase( i );
else
i++;
}
// Print dependencies
if( dependencies.size() > 0 )
{
mPrinter->printLine(stream, "// Dependencies:");
for( S32 i = 0; i < dependencies.size(); i++ )
dependencies[i]->print( stream );
mPrinter->printLine(stream, "");
}
}
void ShaderGen::_printFeatures( Stream &stream )
{
mOutput->print( stream );
}
void ShaderGen::_printVertShader( Stream &stream )
{
mPrinter->printShaderHeader(stream);
_printDependencies(stream); // TODO: Split into vert and pix dependencies?
_printFeatureList(stream);
// print out structures
mComponents[C_VERT_STRUCT]->print( stream, true );
mComponents[C_CONNECTOR]->print( stream, true );
mPrinter->printMainComment(stream);
mComponents[C_VERT_MAIN]->print( stream, true );
mComponents[C_VERT_STRUCT]->printOnMain( stream, true );
// print out the function
_printFeatures( stream );
mPrinter->printVertexShaderCloser(stream);
}
void ShaderGen::_printPixShader( Stream &stream )
{
mPrinter->printShaderHeader(stream);
_printDependencies(stream); // TODO: Split into vert and pix dependencies?
_printFeatureList(stream);
mComponents[C_CONNECTOR]->print( stream, false );
mPrinter->printPixelShaderOutputStruct(stream, mFeatureData);
mPrinter->printMainComment(stream);
mComponents[C_PIX_MAIN]->print( stream, false );
mComponents[C_CONNECTOR]->printOnMain( stream, false );
// print out the function
_printFeatures( stream );
mPrinter->printPixelShaderCloser(stream);
}
GFXShader* ShaderGen::getShader(const MaterialFeatureData& featureData, const GFXVertexFormat* vertexFormat, const Vector<GFXShaderMacro>* macros, const Vector<String>& samplers)
{
PROFILE_SCOPE(ShaderGen_GetShader);
const FeatureSet& features = featureData.codify();
// Build a description string from the features
// and vertex format combination ( and macros ).
String shaderDescription = vertexFormat->getDescription() + features.getDescription();
String cacheKey = Torque::getStringHash64(shaderDescription);
Vector<GFXShaderMacro> shaderMacros;
shaderMacros.push_back(GFXShaderMacro("TORQUE_SHADERGEN"));
if (macros)
shaderMacros.merge(*macros);
ShaderDataMap::iterator dat = mProcShaderData.find(cacheKey);
if (dat != mProcShaderData.end())
{
// should we loop vertex shader features to build mInstancingFormat before sending it down to see old hob?
return dat->value->getShader(shaderMacros);
}
ShaderData* shaderData = new ShaderData;
shaderData->setPixVersion(GFX->getPixelShaderVersion());
for (U32 samp = 0; samp < samplers.size(); samp++)
{
shaderData->setSamplerName(samplers[samp], samp);
}
generateShader(featureData, shaderData, vertexFormat, cacheKey, shaderMacros);
shaderData->setInstancingFormat(&mInstancingFormat);
mProcShaderData.insert(cacheKey, shaderData);
return shaderData->getShader(shaderMacros);
}
void ShaderGen::flushProceduralShaders()
{
// The shaders are reference counted, so we
// just need to clear the map.
mProcShaderData.clear();
}