зеркало из https://github.com/AvaloniaUI/angle.git
add support for EXT_blend_func_extended to D3D11
Change-Id: Id66868851a490d0a68a7e76280720825c4844a45 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/1591192 Commit-Queue: Geoff Lang <geofflang@chromium.org> Reviewed-by: Shahbaz Youssefi <syoussefi@chromium.org>
This commit is contained in:
Родитель
3e62561c4e
Коммит
8ba78da0b3
|
@ -233,6 +233,7 @@ OutputHLSL::OutputHLSL(sh::GLenum shaderType,
|
|||
const char *sourcePath,
|
||||
ShShaderOutput outputType,
|
||||
int numRenderTargets,
|
||||
int maxDualSourceDrawBuffers,
|
||||
const std::vector<Uniform> &uniforms,
|
||||
ShCompileOptions compileOptions,
|
||||
sh::WorkGroupSize workGroupSize,
|
||||
|
@ -249,6 +250,7 @@ OutputHLSL::OutputHLSL(sh::GLenum shaderType,
|
|||
mInsideFunction(false),
|
||||
mInsideMain(false),
|
||||
mNumRenderTargets(numRenderTargets),
|
||||
mMaxDualSourceDrawBuffers(maxDualSourceDrawBuffers),
|
||||
mCurrentFunctionMetadata(nullptr),
|
||||
mWorkGroupSize(workGroupSize),
|
||||
mPerfDiagnostics(perfDiagnostics)
|
||||
|
@ -276,6 +278,7 @@ OutputHLSL::OutputHLSL(sh::GLenum shaderType,
|
|||
mUsesNestedBreak = false;
|
||||
mRequiresIEEEStrictCompiling = false;
|
||||
mUseZeroArray = false;
|
||||
mUsesSecondaryColor = false;
|
||||
|
||||
mUniqueIndex = 0;
|
||||
|
||||
|
@ -630,6 +633,8 @@ void OutputHLSL::header(TInfoSinkBase &out,
|
|||
{
|
||||
const bool usingMRTExtension =
|
||||
IsExtensionEnabled(mExtensionBehavior, TExtension::EXT_draw_buffers);
|
||||
const bool usingBFEExtension =
|
||||
IsExtensionEnabled(mExtensionBehavior, TExtension::EXT_blend_func_extended);
|
||||
|
||||
out << "// Varyings\n";
|
||||
writeReferencedVaryings(out);
|
||||
|
@ -664,6 +669,23 @@ void OutputHLSL::header(TInfoSinkBase &out,
|
|||
}
|
||||
|
||||
out << "};\n";
|
||||
|
||||
if (usingBFEExtension && mUsesSecondaryColor)
|
||||
{
|
||||
out << "static float4 gl_SecondaryColor[" << mMaxDualSourceDrawBuffers
|
||||
<< "] = \n"
|
||||
"{\n";
|
||||
for (int i = 0; i < mMaxDualSourceDrawBuffers; i++)
|
||||
{
|
||||
out << " float4(0, 0, 0, 0)";
|
||||
if (i + 1 != mMaxDualSourceDrawBuffers)
|
||||
{
|
||||
out << ",";
|
||||
}
|
||||
out << "\n";
|
||||
}
|
||||
out << "};\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (mUsesFragDepth)
|
||||
|
@ -781,6 +803,11 @@ void OutputHLSL::header(TInfoSinkBase &out,
|
|||
{
|
||||
out << "#define GL_USES_FRAG_DATA\n";
|
||||
}
|
||||
|
||||
if (mShaderVersion < 300 && usingBFEExtension && mUsesSecondaryColor)
|
||||
{
|
||||
out << "#define GL_USES_SECONDARY_COLOR\n";
|
||||
}
|
||||
}
|
||||
else if (mShaderType == GL_VERTEX_SHADER)
|
||||
{
|
||||
|
@ -1118,6 +1145,16 @@ void OutputHLSL::visitSymbol(TIntermSymbol *node)
|
|||
out << "gl_Color";
|
||||
mUsesFragData = true;
|
||||
}
|
||||
else if (qualifier == EvqSecondaryFragColorEXT)
|
||||
{
|
||||
out << "gl_SecondaryColor[0]";
|
||||
mUsesSecondaryColor = true;
|
||||
}
|
||||
else if (qualifier == EvqSecondaryFragDataEXT)
|
||||
{
|
||||
out << "gl_SecondaryColor";
|
||||
mUsesSecondaryColor = true;
|
||||
}
|
||||
else if (qualifier == EvqFragCoord)
|
||||
{
|
||||
mUsesFragCoord = true;
|
||||
|
|
|
@ -43,6 +43,7 @@ class OutputHLSL : public TIntermTraverser
|
|||
const char *sourcePath,
|
||||
ShShaderOutput outputType,
|
||||
int numRenderTargets,
|
||||
int maxDualSourceDrawBuffers,
|
||||
const std::vector<Uniform> &uniforms,
|
||||
ShCompileOptions compileOptions,
|
||||
sh::WorkGroupSize workGroupSize,
|
||||
|
@ -207,8 +208,10 @@ class OutputHLSL : public TIntermTraverser
|
|||
bool mUsesNestedBreak;
|
||||
bool mRequiresIEEEStrictCompiling;
|
||||
mutable bool mUseZeroArray;
|
||||
bool mUsesSecondaryColor;
|
||||
|
||||
int mNumRenderTargets;
|
||||
int mMaxDualSourceDrawBuffers;
|
||||
|
||||
int mUniqueIndex; // For creating unique names
|
||||
|
||||
|
|
|
@ -42,6 +42,8 @@ void TranslatorHLSL::translate(TIntermBlock *root,
|
|||
{
|
||||
const ShBuiltInResources &resources = getResources();
|
||||
int numRenderTargets = resources.EXT_draw_buffers ? resources.MaxDrawBuffers : 1;
|
||||
int maxDualSourceDrawBuffers =
|
||||
resources.EXT_blend_func_extended ? resources.MaxDualSourceDrawBuffers : 0;
|
||||
|
||||
sh::AddDefaultReturnStatements(root);
|
||||
|
||||
|
@ -135,10 +137,10 @@ void TranslatorHLSL::translate(TIntermBlock *root,
|
|||
sh::RewriteAtomicFunctionExpressions(root, &getSymbolTable(), getShaderVersion());
|
||||
}
|
||||
|
||||
sh::OutputHLSL outputHLSL(getShaderType(), getShaderVersion(), getExtensionBehavior(),
|
||||
getSourcePath(), getOutputType(), numRenderTargets, getUniforms(),
|
||||
compileOptions, getComputeShaderLocalSize(), &getSymbolTable(),
|
||||
perfDiagnostics, mShaderStorageBlocks);
|
||||
sh::OutputHLSL outputHLSL(
|
||||
getShaderType(), getShaderVersion(), getExtensionBehavior(), getSourcePath(),
|
||||
getOutputType(), numRenderTargets, maxDualSourceDrawBuffers, getUniforms(), compileOptions,
|
||||
getComputeShaderLocalSize(), &getSymbolTable(), perfDiagnostics, mShaderStorageBlocks);
|
||||
|
||||
outputHLSL.output(root, getInfoSink().obj);
|
||||
|
||||
|
|
|
@ -59,7 +59,7 @@ HashStream &operator<<(HashStream &stream, const ProgramBindings &bindings)
|
|||
{
|
||||
for (const auto &binding : bindings)
|
||||
{
|
||||
stream << binding.first << binding.second;
|
||||
stream << binding.first << binding.second.location;
|
||||
}
|
||||
return stream;
|
||||
}
|
||||
|
|
|
@ -142,7 +142,11 @@ GLint GetVariableLocation(const std::vector<VarT> &list,
|
|||
|
||||
const VarT &variable = list[variableLocation.index];
|
||||
|
||||
if (angle::BeginsWith(variable.name, name))
|
||||
// Array output variables may be bound out of order, so we need to ensure we only pick the
|
||||
// first element if given the base name. Uniforms don't allow this behavior and some code
|
||||
// seemingly depends on the opposite behavior, so only enable it for output variables.
|
||||
if (angle::BeginsWith(variable.name, name) &&
|
||||
(!std::is_base_of<sh::OutputVariable, VarT>::value || variableLocation.arrayIndex == 0))
|
||||
{
|
||||
if (name.length() == variable.name.length())
|
||||
{
|
||||
|
@ -863,13 +867,56 @@ ProgramBindings::~ProgramBindings() {}
|
|||
|
||||
void ProgramBindings::bindLocation(GLuint index, const std::string &name)
|
||||
{
|
||||
mBindings[name] = index;
|
||||
mBindings[name] = ProgramBinding(index);
|
||||
|
||||
// EXT_blend_func_extended spec: "If it specifies the base name of an array,
|
||||
// it identifies the resources associated with the first element of the array."
|
||||
//
|
||||
// Normalize array bindings so that "name" and "name[0]" map to the same entry.
|
||||
// If this binding is of the form "name[0]", then mark the "name" binding as
|
||||
// aliased but do not update it yet in case "name" is not actually an array.
|
||||
size_t nameLengthWithoutArrayIndex;
|
||||
unsigned int arrayIndex = ParseArrayIndex(name, &nameLengthWithoutArrayIndex);
|
||||
if (arrayIndex == 0)
|
||||
{
|
||||
std::string baseName = name.substr(0u, nameLengthWithoutArrayIndex);
|
||||
auto iter = mBindings.find(baseName);
|
||||
if (iter != mBindings.end())
|
||||
{
|
||||
iter->second.aliased = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int ProgramBindings::getBinding(const std::string &name) const
|
||||
int ProgramBindings::getBindingByName(const std::string &name) const
|
||||
{
|
||||
auto iter = mBindings.find(name);
|
||||
return (iter != mBindings.end()) ? iter->second : -1;
|
||||
return (iter != mBindings.end()) ? iter->second.location : -1;
|
||||
}
|
||||
|
||||
int ProgramBindings::getBinding(const sh::VariableWithLocation &variable) const
|
||||
{
|
||||
const std::string &name = variable.name;
|
||||
|
||||
// Check with the normalized array name if applicable.
|
||||
if (variable.isArray())
|
||||
{
|
||||
size_t nameLengthWithoutArrayIndex;
|
||||
unsigned int arrayIndex = ParseArrayIndex(name, &nameLengthWithoutArrayIndex);
|
||||
if (arrayIndex == 0)
|
||||
{
|
||||
std::string baseName = name.substr(0u, nameLengthWithoutArrayIndex);
|
||||
auto iter = mBindings.find(baseName);
|
||||
// If "name" exists and is not aliased, that means it was modified more
|
||||
// recently than its "name[0]" form and should be used instead of that.
|
||||
if (iter != mBindings.end() && !iter->second.aliased)
|
||||
{
|
||||
return iter->second.location;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return getBindingByName(name);
|
||||
}
|
||||
|
||||
ProgramBindings::const_iterator ProgramBindings::begin() const
|
||||
|
@ -1152,7 +1199,7 @@ BindingInfo Program::getFragmentInputBindingInfo(GLint index) const
|
|||
|
||||
for (const auto &binding : mFragmentInputBindings)
|
||||
{
|
||||
if (binding.second != static_cast<GLuint>(index))
|
||||
if (binding.second.location != static_cast<GLuint>(index))
|
||||
continue;
|
||||
|
||||
ret.valid = true;
|
||||
|
@ -1540,6 +1587,7 @@ void Program::unlink()
|
|||
mState.mAtomicCounterBuffers.clear();
|
||||
mState.mOutputVariables.clear();
|
||||
mState.mOutputLocations.clear();
|
||||
mState.mSecondaryOutputLocations.clear();
|
||||
mState.mOutputVariableTypes.clear();
|
||||
mState.mDrawBufferTypeMask.reset();
|
||||
mState.mActiveOutputVariables.reset();
|
||||
|
@ -2930,7 +2978,7 @@ bool Program::linkValidateFragmentInputBindings(gl::InfoLog &infoLog) const
|
|||
continue;
|
||||
}
|
||||
|
||||
const auto inputBinding = mFragmentInputBindings.getBinding(input.name);
|
||||
const auto inputBinding = mFragmentInputBindings.getBinding(input);
|
||||
if (inputBinding == -1)
|
||||
continue;
|
||||
|
||||
|
@ -3121,7 +3169,7 @@ bool Program::linkAttributes(const Caps &caps, InfoLog &infoLog)
|
|||
// for each member/element (unlike uniforms for example).
|
||||
ASSERT(!attribute.isArray() && !attribute.isStruct());
|
||||
|
||||
int bindingLocation = mAttributeBindings.getBinding(attribute.name);
|
||||
int bindingLocation = mAttributeBindings.getBinding(attribute);
|
||||
if (attribute.location == -1 && bindingLocation != -1)
|
||||
{
|
||||
attribute.location = bindingLocation;
|
||||
|
@ -3710,7 +3758,7 @@ int Program::getOutputLocationForLink(const sh::OutputVariable &outputVariable)
|
|||
{
|
||||
return outputVariable.location;
|
||||
}
|
||||
int apiLocation = mFragmentOutputLocations.getBinding(outputVariable.name);
|
||||
int apiLocation = mFragmentOutputLocations.getBinding(outputVariable);
|
||||
if (apiLocation != -1)
|
||||
{
|
||||
return apiLocation;
|
||||
|
@ -3725,7 +3773,7 @@ bool Program::isOutputSecondaryForLink(const sh::OutputVariable &outputVariable)
|
|||
ASSERT(outputVariable.index == 0 || outputVariable.index == 1);
|
||||
return (outputVariable.index == 1);
|
||||
}
|
||||
int apiIndex = mFragmentOutputIndexes.getBinding(outputVariable.name);
|
||||
int apiIndex = mFragmentOutputIndexes.getBinding(outputVariable);
|
||||
if (apiIndex != -1)
|
||||
{
|
||||
// Index layout qualifier from the shader takes precedence, so the index from the API is
|
||||
|
@ -3737,6 +3785,60 @@ bool Program::isOutputSecondaryForLink(const sh::OutputVariable &outputVariable)
|
|||
return false;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
bool FindUsedOutputLocation(std::vector<VariableLocation> &outputLocations,
|
||||
unsigned int baseLocation,
|
||||
unsigned int elementCount,
|
||||
const std::vector<VariableLocation> &reservedLocations,
|
||||
unsigned int variableIndex)
|
||||
{
|
||||
if (baseLocation + elementCount > outputLocations.size())
|
||||
{
|
||||
elementCount =
|
||||
baseLocation < outputLocations.size() ? outputLocations.size() - baseLocation : 0;
|
||||
}
|
||||
for (unsigned int elementIndex = 0; elementIndex < elementCount; elementIndex++)
|
||||
{
|
||||
const unsigned int location = baseLocation + elementIndex;
|
||||
if (outputLocations[location].used())
|
||||
{
|
||||
VariableLocation locationInfo(elementIndex, variableIndex);
|
||||
if (std::find(reservedLocations.begin(), reservedLocations.end(), locationInfo) ==
|
||||
reservedLocations.end())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void AssignOutputLocations(std::vector<VariableLocation> &outputLocations,
|
||||
unsigned int baseLocation,
|
||||
unsigned int elementCount,
|
||||
const std::vector<VariableLocation> &reservedLocations,
|
||||
unsigned int variableIndex)
|
||||
{
|
||||
if (baseLocation + elementCount > outputLocations.size())
|
||||
{
|
||||
outputLocations.resize(baseLocation + elementCount);
|
||||
}
|
||||
for (unsigned int elementIndex = 0; elementIndex < elementCount; elementIndex++)
|
||||
{
|
||||
VariableLocation locationInfo(elementIndex, variableIndex);
|
||||
if (std::find(reservedLocations.begin(), reservedLocations.end(), locationInfo) ==
|
||||
reservedLocations.end())
|
||||
{
|
||||
const unsigned int location = baseLocation + elementIndex;
|
||||
outputLocations[location] = locationInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
bool Program::linkOutputVariables(const Caps &caps,
|
||||
const Extensions &extensions,
|
||||
const Version &version,
|
||||
|
@ -3821,10 +3923,77 @@ bool Program::linkOutputVariables(const Caps &caps,
|
|||
}
|
||||
}
|
||||
|
||||
bool hasSecondaryOutputs = false;
|
||||
// EXT_blend_func_extended doesn't specify anything related to binding specific elements of an
|
||||
// output array in explicit terms.
|
||||
//
|
||||
// Assuming fragData is an output array, you can defend the position that:
|
||||
// P1) you must support binding "fragData" because it's specified
|
||||
// P2) you must support querying "fragData[x]" because it's specified
|
||||
// P3) you must support binding "fragData[0]" because it's a frequently used pattern
|
||||
//
|
||||
// Then you can make the leap of faith:
|
||||
// P4) you must support binding "fragData[x]" because you support "fragData[0]"
|
||||
// P5) you must support binding "fragData[x]" because you support querying "fragData[x]"
|
||||
//
|
||||
// The spec brings in the "world of arrays" when it mentions binding the arrays and the
|
||||
// automatic binding. Thus it must be interpreted that the thing is not undefined, rather you
|
||||
// must infer the only possible interpretation (?). Note again: this need of interpretation
|
||||
// might be completely off of what GL spec logic is.
|
||||
//
|
||||
// The other complexity is that unless you implement this feature, it's hard to understand what
|
||||
// should happen when the client invokes the feature. You cannot add an additional error as it
|
||||
// is not specified. One can ignore it, but obviously it creates the discrepancies...
|
||||
|
||||
std::vector<VariableLocation> reservedLocations;
|
||||
|
||||
// Process any output API bindings for arrays that don't alias to the first element.
|
||||
for (const auto &binding : mFragmentOutputLocations)
|
||||
{
|
||||
size_t nameLengthWithoutArrayIndex;
|
||||
unsigned int arrayIndex = ParseArrayIndex(binding.first, &nameLengthWithoutArrayIndex);
|
||||
if (arrayIndex == 0 || arrayIndex == GL_INVALID_INDEX)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (unsigned int outputVariableIndex = 0;
|
||||
outputVariableIndex < mState.mOutputVariables.size(); outputVariableIndex++)
|
||||
{
|
||||
const sh::OutputVariable &outputVariable = mState.mOutputVariables[outputVariableIndex];
|
||||
// Check that the binding corresponds to an output array and its array index fits.
|
||||
if (outputVariable.isBuiltIn() || !outputVariable.isArray() ||
|
||||
!angle::BeginsWith(outputVariable.name, binding.first,
|
||||
nameLengthWithoutArrayIndex) ||
|
||||
arrayIndex >= outputVariable.getOutermostArraySize())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the API index that corresponds to this exact binding.
|
||||
// This index may differ from the index used for the array's base.
|
||||
auto &outputLocations = mFragmentOutputIndexes.getBindingByName(binding.first) == 1
|
||||
? mState.mSecondaryOutputLocations
|
||||
: mState.mOutputLocations;
|
||||
unsigned int location = binding.second.location;
|
||||
VariableLocation locationInfo(arrayIndex, outputVariableIndex);
|
||||
if (location >= outputLocations.size())
|
||||
{
|
||||
outputLocations.resize(location + 1);
|
||||
}
|
||||
if (outputLocations[location].used())
|
||||
{
|
||||
mInfoLog << "Location of variable " << outputVariable.name
|
||||
<< " conflicts with another variable.";
|
||||
return false;
|
||||
}
|
||||
outputLocations[location] = locationInfo;
|
||||
|
||||
// Note the array binding location so that it can be skipped later.
|
||||
reservedLocations.push_back(locationInfo);
|
||||
}
|
||||
}
|
||||
|
||||
// Reserve locations for output variables whose location is fixed in the shader or through the
|
||||
// API.
|
||||
// API. Otherwise, the remaining unallocated outputs will be processed later.
|
||||
for (unsigned int outputVariableIndex = 0; outputVariableIndex < mState.mOutputVariables.size();
|
||||
outputVariableIndex++)
|
||||
{
|
||||
|
@ -3834,51 +4003,30 @@ bool Program::linkOutputVariables(const Caps &caps,
|
|||
if (outputVariable.isBuiltIn())
|
||||
continue;
|
||||
|
||||
int baseLocation = getOutputLocationForLink(outputVariable);
|
||||
if (baseLocation == -1)
|
||||
int fixedLocation = getOutputLocationForLink(outputVariable);
|
||||
if (fixedLocation == -1)
|
||||
{
|
||||
// Here we're only reserving locations for variables whose location is fixed.
|
||||
continue;
|
||||
}
|
||||
unsigned int baseLocation = static_cast<unsigned int>(fixedLocation);
|
||||
|
||||
auto *outputLocations = &mState.mOutputLocations;
|
||||
if (isOutputSecondaryForLink(outputVariable))
|
||||
{
|
||||
outputLocations = &mState.mSecondaryOutputLocations;
|
||||
// Note that this check doesn't need to be before checking baseLocation == -1 above. If
|
||||
// an output has an index specified it will always also have the location specified.
|
||||
hasSecondaryOutputs = true;
|
||||
}
|
||||
auto &outputLocations = isOutputSecondaryForLink(outputVariable)
|
||||
? mState.mSecondaryOutputLocations
|
||||
: mState.mOutputLocations;
|
||||
|
||||
// GLSL ES 3.10 section 4.3.6: Output variables cannot be arrays of arrays or arrays of
|
||||
// structures, so we may use getBasicTypeElementCount().
|
||||
unsigned int elementCount = outputVariable.getBasicTypeElementCount();
|
||||
unsigned int outputLocationsNeeded = static_cast<unsigned int>(baseLocation) + elementCount;
|
||||
if (outputLocationsNeeded > outputLocations->size())
|
||||
if (FindUsedOutputLocation(outputLocations, baseLocation, elementCount, reservedLocations,
|
||||
outputVariableIndex))
|
||||
{
|
||||
outputLocations->resize(outputLocationsNeeded);
|
||||
}
|
||||
for (unsigned int elementIndex = 0; elementIndex < elementCount; elementIndex++)
|
||||
{
|
||||
const unsigned int location = static_cast<unsigned int>(baseLocation) + elementIndex;
|
||||
ASSERT(location < outputLocations->size());
|
||||
if (outputLocations->at(location).used())
|
||||
{
|
||||
mInfoLog << "Location of variable " << outputVariable.name
|
||||
<< " conflicts with another variable.";
|
||||
return false;
|
||||
}
|
||||
if (outputVariable.isArray())
|
||||
{
|
||||
(*outputLocations)[location] = VariableLocation(elementIndex, outputVariableIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
VariableLocation locationInfo;
|
||||
locationInfo.index = outputVariableIndex;
|
||||
(*outputLocations)[location] = locationInfo;
|
||||
}
|
||||
mInfoLog << "Location of variable " << outputVariable.name
|
||||
<< " conflicts with another variable.";
|
||||
return false;
|
||||
}
|
||||
AssignOutputLocations(outputLocations, baseLocation, elementCount, reservedLocations,
|
||||
outputVariableIndex);
|
||||
}
|
||||
|
||||
// Here we assign locations for the output variables that don't yet have them. Note that we're
|
||||
|
@ -3887,7 +4035,7 @@ bool Program::linkOutputVariables(const Caps &caps,
|
|||
// we got the output variables. The spec isn't clear on what kind of algorithm is required for
|
||||
// finding locations for the output variables, so this should be acceptable at least for now.
|
||||
GLuint maxLocation = caps.maxDrawBuffers;
|
||||
if (hasSecondaryOutputs)
|
||||
if (!mState.mSecondaryOutputLocations.empty())
|
||||
{
|
||||
// EXT_blend_func_extended: Program outputs will be validated against
|
||||
// MAX_DUAL_SOURCE_DRAW_BUFFERS_EXT if there's even one output with index one.
|
||||
|
@ -3903,74 +4051,46 @@ bool Program::linkOutputVariables(const Caps &caps,
|
|||
if (outputVariable.isBuiltIn())
|
||||
continue;
|
||||
|
||||
if (getOutputLocationForLink(outputVariable) != -1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
auto *outputLocations = &mState.mOutputLocations;
|
||||
if (isOutputSecondaryForLink(outputVariable))
|
||||
{
|
||||
outputLocations = &mState.mSecondaryOutputLocations;
|
||||
}
|
||||
|
||||
int baseLocation = 0;
|
||||
int fixedLocation = getOutputLocationForLink(outputVariable);
|
||||
auto &outputLocations = isOutputSecondaryForLink(outputVariable)
|
||||
? mState.mSecondaryOutputLocations
|
||||
: mState.mOutputLocations;
|
||||
unsigned int baseLocation = 0;
|
||||
unsigned int elementCount = outputVariable.getBasicTypeElementCount();
|
||||
bool elementsFit = false;
|
||||
while (!elementsFit)
|
||||
if (fixedLocation != -1)
|
||||
{
|
||||
// Secondary inputs might have caused the max location to drop below what has already
|
||||
// been explicitly assigned locations. Check for any fixed locations above the max
|
||||
// that should cause linking to fail.
|
||||
baseLocation = static_cast<unsigned int>(fixedLocation);
|
||||
}
|
||||
else
|
||||
{
|
||||
// No fixed location, so try to fit the output in unassigned locations.
|
||||
// Try baseLocations starting from 0 one at a time and see if the variable fits.
|
||||
elementsFit = true;
|
||||
if (baseLocation + elementCount > maxLocation)
|
||||
while (FindUsedOutputLocation(outputLocations, baseLocation, elementCount,
|
||||
reservedLocations, outputVariableIndex))
|
||||
{
|
||||
// EXT_blend_func_extended: Linking can fail:
|
||||
// "if the explicit binding assignments do not leave enough space for the linker to
|
||||
// automatically assign a location for a varying out array, which requires multiple
|
||||
// contiguous locations."
|
||||
mInfoLog << "Could not fit output variable into available locations: "
|
||||
<< outputVariable.name;
|
||||
return false;
|
||||
}
|
||||
unsigned int outputLocationsNeeded =
|
||||
static_cast<unsigned int>(baseLocation) + elementCount;
|
||||
if (outputLocationsNeeded > outputLocations->size())
|
||||
{
|
||||
outputLocations->resize(outputLocationsNeeded);
|
||||
}
|
||||
for (unsigned int elementIndex = 0; elementIndex < elementCount; elementIndex++)
|
||||
{
|
||||
const unsigned int location =
|
||||
static_cast<unsigned int>(baseLocation) + elementIndex;
|
||||
ASSERT(location < outputLocations->size());
|
||||
if (outputLocations->at(location).used())
|
||||
{
|
||||
elementsFit = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (elementsFit)
|
||||
{
|
||||
for (unsigned int elementIndex = 0; elementIndex < elementCount; elementIndex++)
|
||||
{
|
||||
const unsigned int location =
|
||||
static_cast<unsigned int>(baseLocation) + elementIndex;
|
||||
if (outputVariable.isArray())
|
||||
{
|
||||
(*outputLocations)[location] =
|
||||
VariableLocation(elementIndex, outputVariableIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
VariableLocation locationInfo;
|
||||
locationInfo.index = outputVariableIndex;
|
||||
(*outputLocations)[location] = locationInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
++baseLocation;
|
||||
baseLocation++;
|
||||
}
|
||||
AssignOutputLocations(outputLocations, baseLocation, elementCount, reservedLocations,
|
||||
outputVariableIndex);
|
||||
}
|
||||
|
||||
// Check for any elements assigned above the max location that are actually used.
|
||||
if (baseLocation + elementCount > maxLocation &&
|
||||
(baseLocation >= maxLocation ||
|
||||
FindUsedOutputLocation(outputLocations, maxLocation,
|
||||
baseLocation + elementCount - maxLocation, reservedLocations,
|
||||
outputVariableIndex)))
|
||||
{
|
||||
// EXT_blend_func_extended: Linking can fail:
|
||||
// "if the explicit binding assignments do not leave enough space for the linker to
|
||||
// automatically assign a location for a varying out array, which requires multiple
|
||||
// contiguous locations."
|
||||
mInfoLog << "Could not fit output variable into available locations: "
|
||||
<< outputVariable.name;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -179,6 +179,11 @@ struct VariableLocation
|
|||
void markUnused() { index = kUnused; }
|
||||
void markIgnored() { ignored = true; }
|
||||
|
||||
bool operator==(const VariableLocation &other) const
|
||||
{
|
||||
return arrayIndex == other.arrayIndex && index == other.index;
|
||||
}
|
||||
|
||||
// "arrayIndex" stores the index of the innermost GLSL array. It's zero for non-arrays.
|
||||
unsigned int arrayIndex;
|
||||
// "index" is an index of the variable. The variable contains the indices for other than the
|
||||
|
@ -473,6 +478,16 @@ class ProgramState final : angle::NonCopyable
|
|||
ActiveTextureMask mActiveImagesMask;
|
||||
};
|
||||
|
||||
struct ProgramBinding
|
||||
{
|
||||
ProgramBinding() : location(GL_INVALID_INDEX), aliased(false) {}
|
||||
ProgramBinding(GLuint index) : location(index), aliased(false) {}
|
||||
|
||||
GLuint location;
|
||||
// Whether another binding was set that may potentially alias this.
|
||||
bool aliased;
|
||||
};
|
||||
|
||||
class ProgramBindings final : angle::NonCopyable
|
||||
{
|
||||
public:
|
||||
|
@ -480,14 +495,15 @@ class ProgramBindings final : angle::NonCopyable
|
|||
~ProgramBindings();
|
||||
|
||||
void bindLocation(GLuint index, const std::string &name);
|
||||
int getBinding(const std::string &name) const;
|
||||
int getBindingByName(const std::string &name) const;
|
||||
int getBinding(const sh::VariableWithLocation &variable) const;
|
||||
|
||||
using const_iterator = std::unordered_map<std::string, GLuint>::const_iterator;
|
||||
using const_iterator = std::unordered_map<std::string, ProgramBinding>::const_iterator;
|
||||
const_iterator begin() const;
|
||||
const_iterator end() const;
|
||||
|
||||
private:
|
||||
std::unordered_map<std::string, GLuint> mBindings;
|
||||
std::unordered_map<std::string, ProgramBinding> mBindings;
|
||||
};
|
||||
|
||||
struct ProgramVaryingRef
|
||||
|
|
|
@ -32,20 +32,6 @@ LinkedUniform *FindUniform(std::vector<LinkedUniform> &list, const std::string &
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
int GetUniformLocationBinding(const ProgramBindings &uniformLocationBindings,
|
||||
const sh::Uniform &uniform)
|
||||
{
|
||||
int binding = uniformLocationBindings.getBinding(uniform.name);
|
||||
if (uniform.isArray() && binding == -1)
|
||||
{
|
||||
// Bindings for array uniforms can be set either with or without [0] in the end.
|
||||
ASSERT(angle::EndsWith(uniform.name, "[0]"));
|
||||
std::string nameWithoutIndex = uniform.name.substr(0u, uniform.name.length() - 3u);
|
||||
return uniformLocationBindings.getBinding(nameWithoutIndex);
|
||||
}
|
||||
return binding;
|
||||
}
|
||||
|
||||
template <typename VarT>
|
||||
void SetActive(std::vector<VarT> *list, const std::string &name, ShaderType shaderType, bool active)
|
||||
{
|
||||
|
@ -664,7 +650,7 @@ bool UniformLinker::indexUniforms(InfoLog &infoLog, const ProgramBindings &unifo
|
|||
continue;
|
||||
}
|
||||
|
||||
int preSetLocation = GetUniformLocationBinding(uniformLocationBindings, uniform);
|
||||
int preSetLocation = uniformLocationBindings.getBinding(uniform);
|
||||
int shaderLocation = uniform.location;
|
||||
|
||||
if (shaderLocation != -1)
|
||||
|
@ -740,7 +726,7 @@ bool UniformLinker::gatherUniformLocationsAndCheckConflicts(
|
|||
continue;
|
||||
}
|
||||
|
||||
int apiBoundLocation = GetUniformLocationBinding(uniformLocationBindings, uniform);
|
||||
int apiBoundLocation = uniformLocationBindings.getBinding(uniform);
|
||||
int shaderLocation = uniform.location;
|
||||
|
||||
if (shaderLocation != -1)
|
||||
|
@ -785,7 +771,7 @@ bool UniformLinker::gatherUniformLocationsAndCheckConflicts(
|
|||
// from the shader. Other uniforms should not be assigned to those locations.
|
||||
for (const auto &locationBinding : uniformLocationBindings)
|
||||
{
|
||||
GLuint location = locationBinding.second;
|
||||
GLuint location = locationBinding.second.location;
|
||||
if (reservedLocations.find(location) == reservedLocations.end())
|
||||
{
|
||||
ignoredLocations->insert(location);
|
||||
|
|
|
@ -97,13 +97,14 @@ void HLSLTypeString(std::ostringstream &ostream, GLenum type)
|
|||
|
||||
const PixelShaderOutputVariable *FindOutputAtLocation(
|
||||
const std::vector<PixelShaderOutputVariable> &outputVariables,
|
||||
unsigned int location)
|
||||
unsigned int location,
|
||||
size_t index = 0)
|
||||
{
|
||||
for (size_t variableIndex = 0; variableIndex < outputVariables.size(); ++variableIndex)
|
||||
for (auto &outputVar : outputVariables)
|
||||
{
|
||||
if (outputVariables[variableIndex].outputIndex == location)
|
||||
if (outputVar.outputLocation == location && outputVar.outputIndex == index)
|
||||
{
|
||||
return &outputVariables[variableIndex];
|
||||
return &outputVar;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -294,7 +295,9 @@ std::string DynamicHLSL::generatePixelShaderForOutputSignature(
|
|||
{
|
||||
numOutputs = 1u;
|
||||
}
|
||||
const PixelShaderOutputVariable defaultOutput(GL_FLOAT_VEC4, "dummy", "float4(0, 0, 0, 1)", 0);
|
||||
const PixelShaderOutputVariable defaultOutput(GL_FLOAT_VEC4, "dummy", "float4(0, 0, 0, 1)", 0,
|
||||
0);
|
||||
size_t outputIndex = 0;
|
||||
|
||||
for (size_t layoutIndex = 0; layoutIndex < numOutputs; ++layoutIndex)
|
||||
{
|
||||
|
@ -303,15 +306,16 @@ std::string DynamicHLSL::generatePixelShaderForOutputSignature(
|
|||
if (binding != GL_NONE)
|
||||
{
|
||||
unsigned int location = (binding - GL_COLOR_ATTACHMENT0);
|
||||
outputIndex =
|
||||
layoutIndex > 0 && binding == outputLayout[layoutIndex - 1] ? outputIndex + 1 : 0;
|
||||
|
||||
const PixelShaderOutputVariable *outputVariable =
|
||||
outputLayout.empty() ? &defaultOutput
|
||||
: FindOutputAtLocation(outputVariables, location);
|
||||
: FindOutputAtLocation(outputVariables, location, outputIndex);
|
||||
|
||||
// OpenGL ES 3.0 spec $4.2.1
|
||||
// If [...] not all user-defined output variables are written, the values of fragment
|
||||
// colors
|
||||
// corresponding to unwritten variables are similarly undefined.
|
||||
// colors corresponding to unwritten variables are similarly undefined.
|
||||
if (outputVariable)
|
||||
{
|
||||
declarationStream << " ";
|
||||
|
@ -1204,10 +1208,26 @@ void DynamicHLSL::getPixelShaderOutputKey(const gl::State &data,
|
|||
outputKeyVariable.name = "gl_Color" + Str(renderTargetIndex);
|
||||
outputKeyVariable.source =
|
||||
broadcast ? "gl_Color[0]" : "gl_Color[" + Str(renderTargetIndex) + "]";
|
||||
outputKeyVariable.outputIndex = renderTargetIndex;
|
||||
outputKeyVariable.outputLocation = renderTargetIndex;
|
||||
|
||||
outPixelShaderKey->push_back(outputKeyVariable);
|
||||
}
|
||||
|
||||
if (metadata.usesSecondaryColor())
|
||||
{
|
||||
for (unsigned int secondaryIndex = 0;
|
||||
secondaryIndex < data.getExtensions().maxDualSourceDrawBuffers; secondaryIndex++)
|
||||
{
|
||||
PixelShaderOutputVariable outputKeyVariable;
|
||||
outputKeyVariable.type = GL_FLOAT_VEC4;
|
||||
outputKeyVariable.name = "gl_SecondaryColor" + Str(secondaryIndex);
|
||||
outputKeyVariable.source = "gl_SecondaryColor[" + Str(secondaryIndex) + "]";
|
||||
outputKeyVariable.outputLocation = secondaryIndex;
|
||||
outputKeyVariable.outputIndex = 1;
|
||||
|
||||
outPixelShaderKey->push_back(outputKeyVariable);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1238,7 +1258,39 @@ void DynamicHLSL::getPixelShaderOutputKey(const gl::State &data,
|
|||
outputKeyVariable.source =
|
||||
variableName +
|
||||
(outputVariable.isArray() ? ArrayString(outputLocation.arrayIndex) : "");
|
||||
outputKeyVariable.outputIndex = outputLocationIndex;
|
||||
outputKeyVariable.outputLocation = outputLocationIndex;
|
||||
|
||||
outPixelShaderKey->push_back(outputKeyVariable);
|
||||
}
|
||||
|
||||
// Now generate any secondary outputs...
|
||||
for (size_t outputLocationIndex = 0u;
|
||||
outputLocationIndex < programData.getSecondaryOutputLocations().size();
|
||||
++outputLocationIndex)
|
||||
{
|
||||
const VariableLocation &outputLocation =
|
||||
programData.getSecondaryOutputLocations().at(outputLocationIndex);
|
||||
if (!outputLocation.used())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const sh::ShaderVariable &outputVariable = shaderOutputVars[outputLocation.index];
|
||||
const std::string &variableName = "out_" + outputVariable.name;
|
||||
|
||||
// Fragment outputs can't be arrays of arrays. ESSL 3.10 section 4.3.6.
|
||||
const std::string &elementString =
|
||||
(outputVariable.isArray() ? Str(outputLocation.arrayIndex) : "");
|
||||
|
||||
ASSERT(outputVariable.active);
|
||||
|
||||
PixelShaderOutputVariable outputKeyVariable;
|
||||
outputKeyVariable.type = outputVariable.type;
|
||||
outputKeyVariable.name = variableName + elementString;
|
||||
outputKeyVariable.source =
|
||||
variableName +
|
||||
(outputVariable.isArray() ? ArrayString(outputLocation.arrayIndex) : "");
|
||||
outputKeyVariable.outputLocation = outputLocationIndex;
|
||||
outputKeyVariable.outputIndex = 1;
|
||||
|
||||
outPixelShaderKey->push_back(outputKeyVariable);
|
||||
}
|
||||
|
|
|
@ -67,13 +67,19 @@ struct PixelShaderOutputVariable
|
|||
PixelShaderOutputVariable(GLenum typeIn,
|
||||
const std::string &nameIn,
|
||||
const std::string &sourceIn,
|
||||
size_t outputLocationIn,
|
||||
size_t outputIndexIn)
|
||||
: type(typeIn), name(nameIn), source(sourceIn), outputIndex(outputIndexIn)
|
||||
: type(typeIn),
|
||||
name(nameIn),
|
||||
source(sourceIn),
|
||||
outputLocation(outputLocationIn),
|
||||
outputIndex(outputIndexIn)
|
||||
{}
|
||||
|
||||
GLenum type = GL_NONE;
|
||||
std::string name;
|
||||
std::string source;
|
||||
size_t outputLocation = 0;
|
||||
size_t outputIndex = 0;
|
||||
};
|
||||
|
||||
|
|
|
@ -67,6 +67,20 @@ void GetDefaultInputLayoutFromShader(gl::Shader *vertexShader, gl::InputLayout *
|
|||
}
|
||||
}
|
||||
|
||||
size_t GetMaxOutputIndex(const std::vector<PixelShaderOutputVariable> &shaderOutputVars,
|
||||
size_t location)
|
||||
{
|
||||
size_t maxIndex = 0;
|
||||
for (auto &outputVar : shaderOutputVars)
|
||||
{
|
||||
if (outputVar.outputLocation == location)
|
||||
{
|
||||
maxIndex = std::max(maxIndex, outputVar.outputIndex);
|
||||
}
|
||||
}
|
||||
return maxIndex;
|
||||
}
|
||||
|
||||
void GetDefaultOutputLayoutFromShader(
|
||||
const std::vector<PixelShaderOutputVariable> &shaderOutputVars,
|
||||
std::vector<GLenum> *outputLayoutOut)
|
||||
|
@ -75,8 +89,10 @@ void GetDefaultOutputLayoutFromShader(
|
|||
|
||||
if (!shaderOutputVars.empty())
|
||||
{
|
||||
outputLayoutOut->push_back(GL_COLOR_ATTACHMENT0 +
|
||||
static_cast<unsigned int>(shaderOutputVars[0].outputIndex));
|
||||
size_t location = shaderOutputVars[0].outputLocation;
|
||||
size_t maxIndex = GetMaxOutputIndex(shaderOutputVars, location);
|
||||
outputLayoutOut->assign(maxIndex + 1,
|
||||
GL_COLOR_ATTACHMENT0 + static_cast<unsigned int>(location));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -477,6 +493,11 @@ bool ProgramD3DMetadata::usesBroadcast(const gl::State &data) const
|
|||
data.getClientMajorVersion() < 3);
|
||||
}
|
||||
|
||||
bool ProgramD3DMetadata::usesSecondaryColor() const
|
||||
{
|
||||
return mAttachedShaders[gl::ShaderType::Fragment]->usesSecondaryColor();
|
||||
}
|
||||
|
||||
bool ProgramD3DMetadata::usesFragDepth() const
|
||||
{
|
||||
return mAttachedShaders[gl::ShaderType::Fragment]->usesFragDepth();
|
||||
|
@ -1166,6 +1187,7 @@ std::unique_ptr<rx::LinkEvent> ProgramD3D::load(const gl::Context *context,
|
|||
stream->readInt(&mPixelShaderKey[pixelShaderKeyIndex].type);
|
||||
stream->readString(&mPixelShaderKey[pixelShaderKeyIndex].name);
|
||||
stream->readString(&mPixelShaderKey[pixelShaderKeyIndex].source);
|
||||
stream->readInt(&mPixelShaderKey[pixelShaderKeyIndex].outputLocation);
|
||||
stream->readInt(&mPixelShaderKey[pixelShaderKeyIndex].outputIndex);
|
||||
}
|
||||
|
||||
|
@ -1449,6 +1471,7 @@ void ProgramD3D::save(const gl::Context *context, gl::BinaryOutputStream *stream
|
|||
stream->writeInt(variable.type);
|
||||
stream->writeString(variable.name);
|
||||
stream->writeString(variable.source);
|
||||
stream->writeInt(variable.outputLocation);
|
||||
stream->writeInt(variable.outputIndex);
|
||||
}
|
||||
|
||||
|
@ -3048,7 +3071,11 @@ void ProgramD3D::updateCachedOutputLayout(const gl::Context *context,
|
|||
{
|
||||
auto binding = colorbuffer->getBinding() == GL_BACK ? GL_COLOR_ATTACHMENT0
|
||||
: colorbuffer->getBinding();
|
||||
mPixelShaderOutputLayoutCache.push_back(binding);
|
||||
size_t maxIndex = binding != GL_NONE ? GetMaxOutputIndex(mPixelShaderKey,
|
||||
binding - GL_COLOR_ATTACHMENT0)
|
||||
: 0;
|
||||
mPixelShaderOutputLayoutCache.insert(mPixelShaderOutputLayoutCache.end(), maxIndex + 1,
|
||||
binding);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -122,6 +122,7 @@ class ProgramD3DMetadata final : angle::NonCopyable
|
|||
|
||||
int getRendererMajorShaderModel() const;
|
||||
bool usesBroadcast(const gl::State &data) const;
|
||||
bool usesSecondaryColor() const;
|
||||
bool usesFragDepth() const;
|
||||
bool usesPointCoord() const;
|
||||
bool usesFragCoord() const;
|
||||
|
|
|
@ -150,6 +150,7 @@ void ShaderD3D::uncompile()
|
|||
mUsesMultipleRenderTargets = false;
|
||||
mUsesFragColor = false;
|
||||
mUsesFragData = false;
|
||||
mUsesSecondaryColor = false;
|
||||
mUsesFragCoord = false;
|
||||
mUsesFrontFacing = false;
|
||||
mUsesPointSize = false;
|
||||
|
@ -272,6 +273,7 @@ std::shared_ptr<WaitableCompileEvent> ShaderD3D::compile(const gl::Context *cont
|
|||
mUsesMultipleRenderTargets = translatedSource.find("GL_USES_MRT") != std::string::npos;
|
||||
mUsesFragColor = translatedSource.find("GL_USES_FRAG_COLOR") != std::string::npos;
|
||||
mUsesFragData = translatedSource.find("GL_USES_FRAG_DATA") != std::string::npos;
|
||||
mUsesSecondaryColor = translatedSource.find("GL_USES_SECONDARY_COLOR") != std::string::npos;
|
||||
mUsesFragCoord = translatedSource.find("GL_USES_FRAG_COORD") != std::string::npos;
|
||||
mUsesFrontFacing = translatedSource.find("GL_USES_FRONT_FACING") != std::string::npos;
|
||||
mUsesPointSize = translatedSource.find("GL_USES_POINT_SIZE") != std::string::npos;
|
||||
|
|
|
@ -65,6 +65,7 @@ class ShaderD3D : public ShaderImpl
|
|||
bool usesMultipleRenderTargets() const { return mUsesMultipleRenderTargets; }
|
||||
bool usesFragColor() const { return mUsesFragColor; }
|
||||
bool usesFragData() const { return mUsesFragData; }
|
||||
bool usesSecondaryColor() const { return mUsesSecondaryColor; }
|
||||
bool usesFragCoord() const { return mUsesFragCoord; }
|
||||
bool usesFrontFacing() const { return mUsesFrontFacing; }
|
||||
bool usesPointSize() const { return mUsesPointSize; }
|
||||
|
@ -81,6 +82,7 @@ class ShaderD3D : public ShaderImpl
|
|||
bool mUsesMultipleRenderTargets;
|
||||
bool mUsesFragColor;
|
||||
bool mUsesFragData;
|
||||
bool mUsesSecondaryColor;
|
||||
bool mUsesFragCoord;
|
||||
bool mUsesFrontFacing;
|
||||
bool mUsesPointSize;
|
||||
|
|
|
@ -1649,6 +1649,8 @@ void GenerateCaps(ID3D11Device *device,
|
|||
extensions->textureBorderClamp = true;
|
||||
extensions->textureMultisample = true;
|
||||
extensions->provokingVertex = true;
|
||||
extensions->blendFuncExtended = true;
|
||||
extensions->maxDualSourceDrawBuffers = 1;
|
||||
|
||||
// D3D11 Feature Level 10_0+ uses SV_IsFrontFace in HLSL to emulate gl_FrontFacing.
|
||||
// D3D11 Feature Level 9_3 doesn't support SV_IsFrontFace, and has no equivalent, so can't
|
||||
|
@ -1751,6 +1753,18 @@ D3D11_BLEND ConvertBlendFunc(GLenum glBlend, bool isAlpha)
|
|||
case GL_SRC_ALPHA_SATURATE:
|
||||
d3dBlend = D3D11_BLEND_SRC_ALPHA_SAT;
|
||||
break;
|
||||
case GL_SRC1_COLOR_EXT:
|
||||
d3dBlend = (isAlpha ? D3D11_BLEND_SRC1_ALPHA : D3D11_BLEND_SRC1_COLOR);
|
||||
break;
|
||||
case GL_SRC1_ALPHA_EXT:
|
||||
d3dBlend = D3D11_BLEND_SRC1_ALPHA;
|
||||
break;
|
||||
case GL_ONE_MINUS_SRC1_COLOR_EXT:
|
||||
d3dBlend = (isAlpha ? D3D11_BLEND_INV_SRC1_ALPHA : D3D11_BLEND_INV_SRC1_COLOR);
|
||||
break;
|
||||
case GL_ONE_MINUS_SRC1_ALPHA_EXT:
|
||||
d3dBlend = D3D11_BLEND_INV_SRC1_ALPHA;
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче