487 lines
16 KiB
C++
487 lines
16 KiB
C++
//========= Copyright Valve Corporation, All rights reserved. ============//
|
|
// TOGL CODE LICENSE
|
|
//
|
|
// Copyright 2011-2014 Valve Corporation
|
|
// All Rights Reserved.
|
|
//
|
|
// 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.
|
|
//
|
|
// cglmprogram.h
|
|
// GLMgr programs (ARBVP/ARBfp)
|
|
//
|
|
//===============================================================================
|
|
|
|
#ifndef CGLMPROGRAM_H
|
|
#define CGLMPROGRAM_H
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#pragma once
|
|
|
|
// good ARB program references
|
|
// http://petewarden.com/notes/archives/2005/05/fragment_progra_2.html
|
|
// http://petewarden.com/notes/archives/2005/06/fragment_progra_3.html
|
|
|
|
// ext links
|
|
|
|
// http://www.opengl.org/registry/specs/ARB/vertex_program.txt
|
|
// http://www.opengl.org/registry/specs/ARB/fragment_program.txt
|
|
// http://www.opengl.org/registry/specs/EXT/gpu_program_parameters.txt
|
|
|
|
//===============================================================================
|
|
|
|
// tokens not in the SDK headers
|
|
|
|
//#ifndef GL_DEPTH_STENCIL_ATTACHMENT_EXT
|
|
// #define GL_DEPTH_STENCIL_ATTACHMENT_EXT 0x84F9
|
|
//#endif
|
|
|
|
//===============================================================================
|
|
|
|
// forward declarations
|
|
|
|
class GLMContext;
|
|
class CGLMShaderPair;
|
|
class CGLMShaderPairCache;
|
|
|
|
// CGLMProgram can contain two flavors of the same program, one in assembler,
|
|
// one in GLSL. these flavors are pretty different in terms of the API's that
|
|
// are used to activate them - for example, assembler programs can just get
|
|
// bound to the context, whereas GLSL programs have to be linked. To some
|
|
// extent we try to hide that detail inside GLM.
|
|
|
|
// for now, make CGLMProgram a container, it does not set policy or hold a
|
|
// preference as to which flavor you want to use. GLMContext has to handle
|
|
// that.
|
|
|
|
enum EGLMProgramType {
|
|
kGLMVertexProgram,
|
|
kGLMFragmentProgram,
|
|
|
|
kGLMNumProgramTypes
|
|
};
|
|
|
|
enum EGLMProgramLang {
|
|
kGLMARB,
|
|
kGLMGLSL,
|
|
|
|
kGLMNumProgramLangs
|
|
};
|
|
|
|
struct GLMShaderDesc {
|
|
union {
|
|
GLuint arb; // ARB program object name
|
|
GLhandleARB glsl; // GLSL shader object handle (void*)
|
|
} m_object;
|
|
|
|
// these can change if shader text is edited
|
|
bool m_textPresent; // is this flavor(lang) of text present in the buffer?
|
|
int m_textOffset; // where is it
|
|
int m_textLength; // how big
|
|
|
|
bool m_compiled; // has this text been through a compile attempt
|
|
bool m_valid; // and if so, was the compile successful
|
|
|
|
int m_slowMark; // has it been flagged during a non native draw batch
|
|
// before. increment every time it's slow.
|
|
|
|
int m_highWater; // count of vec4's in the major uniform array ("vc" on vs,
|
|
// "pc" on ps) written by dxabstract.... gross!
|
|
int m_VSHighWaterBone; // count of vec4's in the bone-specific uniform
|
|
// array (only valid for vertex shaders)
|
|
};
|
|
|
|
GLenum GLMProgTypeToARBEnum(
|
|
EGLMProgramType type); // map vert/frag to ARB asm bind target
|
|
GLenum GLMProgTypeToGLSLEnum(
|
|
EGLMProgramType type); // map vert/frag to ARB asm bind target
|
|
|
|
#define GL_SHADER_PAIR_CACHE_STATS 0
|
|
|
|
class CGLMProgram {
|
|
public:
|
|
friend class CGLMShaderPairCache;
|
|
friend class CGLMShaderPair;
|
|
friend class GLMContext; // only GLMContext can make CGLMProgram objects
|
|
friend class GLMTester;
|
|
friend struct IDirect3D9;
|
|
friend struct IDirect3DDevice9;
|
|
|
|
//===============================
|
|
|
|
// constructor is very light, it just makes one empty program object per
|
|
// flavor.
|
|
CGLMProgram(GLMContext *ctx, EGLMProgramType type);
|
|
~CGLMProgram();
|
|
|
|
void SetProgramText(char *text); // import text to GLM object - invalidate
|
|
// any prev compiled program
|
|
void SetShaderName(
|
|
const char *name); // only used for debugging/telemetry markup
|
|
|
|
void CompileActiveSources(
|
|
void); // compile only the flavors that were provided.
|
|
void Compile(EGLMProgramLang lang);
|
|
bool CheckValidity(EGLMProgramLang lang);
|
|
|
|
void LogSlow(
|
|
EGLMProgramLang lang); // detailed spew when called for first time; one
|
|
// liner or perhaps silence after that
|
|
|
|
void GetLabelIndexCombo(char *labelOut, int labelOutMaxChars, int *indexOut,
|
|
int *comboOut);
|
|
void GetComboIndexNameString(
|
|
char *stringOut, int stringOutMaxChars); // mmmmmmmm-nnnnnnnn-filename
|
|
|
|
#if GLMDEBUG
|
|
bool PollForChanges(void); // check mirror for changes.
|
|
void ReloadStringFromEditable(
|
|
void); // populate m_string from editable item (react to change)
|
|
bool SyncWithEditable(void);
|
|
#endif
|
|
|
|
//===============================
|
|
|
|
// common stuff
|
|
|
|
GLMContext *m_ctx; // link back to parent context
|
|
|
|
EGLMProgramType m_type; // vertex or pixel
|
|
|
|
uint m_nHashTag; // serial number for hashing
|
|
|
|
char *
|
|
m_text; // copy of text passed into constructor. Can change if
|
|
// editable shaders is enabled. note - it can contain multiple
|
|
// flavors, so use CGLMTextSectioner to scan it and locate them
|
|
#if GLMDEBUG
|
|
CGLMEditableTextItem *m_editable; // editable text item for debugging
|
|
#endif
|
|
|
|
GLMShaderDesc m_descs[kGLMNumProgramLangs];
|
|
|
|
uint m_samplerMask; // (1<<n) mask of sampler active locs, if this is a
|
|
// fragment shader (dxabstract sets this field)
|
|
uint m_samplerTypes; // SAMPLER_2D, etc.
|
|
uint m_fragDataMask; // (1<<n) mask of gl_FragData[n] outputs referenced,
|
|
// if this is a fragment shader (dxabstract sets this
|
|
// field)
|
|
uint m_numDrawBuffers; // number of draw buffers used
|
|
GLenum m_drawBuffers[4]; // GL_COLOR_ATTACHMENT0_EXT1, etc
|
|
uint m_nNumUsedSamplers;
|
|
uint m_maxSamplers;
|
|
uint m_maxVertexAttrs;
|
|
uint m_nCentroidMask;
|
|
uint m_nShadowDepthSamplerMask;
|
|
|
|
bool m_bTranslatedProgram;
|
|
|
|
char m_shaderName[64];
|
|
|
|
// Cache label string from the shader text
|
|
// example:
|
|
// trans#2871 label:vs-file vertexlit_and_unlit_generic_vs20 vs-index 294912
|
|
// vs-combo 1234
|
|
char m_labelName[1024];
|
|
int m_labelIndex;
|
|
int m_labelCombo;
|
|
};
|
|
|
|
//===============================================================================
|
|
|
|
struct GLMShaderPairInfo {
|
|
int m_status; // -1 means req'd index was out of bounds (loop stop..) 0
|
|
// means not present. 1 means present/active.
|
|
|
|
char m_vsName[128];
|
|
int m_vsStaticIndex;
|
|
int m_vsDynamicIndex;
|
|
|
|
char m_psName[128];
|
|
int m_psStaticIndex;
|
|
int m_psDynamicIndex;
|
|
};
|
|
|
|
class CGLMShaderPair // a container for a linked GLSL shader pair, and metadata
|
|
// obtained post-link
|
|
{
|
|
public:
|
|
friend class CGLMProgram;
|
|
friend class GLMContext;
|
|
friend class CGLMShaderPairCache;
|
|
|
|
//===============================
|
|
|
|
// constructor just sets up a GLSL program object and leaves it empty.
|
|
CGLMShaderPair(GLMContext *ctx);
|
|
~CGLMShaderPair();
|
|
|
|
bool SetProgramPair(CGLMProgram *vp, CGLMProgram *fp);
|
|
// true result means successful link and query
|
|
// Note that checking the link status and querying the uniform can be
|
|
// optionally deferred to take advantage of multi-threaded compilation in
|
|
// the driver
|
|
|
|
bool RefreshProgramPair(void);
|
|
// re-link and re-query the uniforms
|
|
|
|
bool ValidateProgramPair(void);
|
|
// true result means successful link and query
|
|
|
|
FORCEINLINE void UpdateScreenUniform(uint nWidthHeight) {
|
|
if (m_nScreenWidthHeight == nWidthHeight) return;
|
|
|
|
m_nScreenWidthHeight = nWidthHeight;
|
|
|
|
float fWidth = (float)(nWidthHeight & 0xFFFF),
|
|
fHeight = (float)(nWidthHeight >> 16);
|
|
// Apply half pixel offset to output vertices to account for the pixel
|
|
// center difference between D3D9 and OpenGL. We output vertices in clip
|
|
// space, which ranges from [-1,1], so 1.0/width in clip space
|
|
// transforms into .5/width in screenspace, see: "Viewports and Clipping
|
|
// (Direct3D 9)" in the DXSDK
|
|
float v[4] = {1.0f / fWidth, 1.0f / fHeight, fWidth, fHeight};
|
|
if (m_locVertexScreenParams >= 0)
|
|
gGL->glUniform4fv(m_locVertexScreenParams, 1, v);
|
|
}
|
|
|
|
//===============================
|
|
|
|
// common stuff
|
|
|
|
GLMContext *m_ctx; // link back to parent context
|
|
|
|
CGLMProgram *m_vertexProg;
|
|
CGLMProgram *m_fragmentProg;
|
|
|
|
GLhandleARB m_program; // linked program object
|
|
|
|
// need meta data for attribs / samplers / params
|
|
// actually we only need it for samplers and params.
|
|
// attributes are hardwired.
|
|
|
|
// vertex stage uniforms
|
|
GLint m_locVertexParams; // "vc" per dx9asmtogl2 convention
|
|
GLint m_locVertexBoneParams; // "vcbones"
|
|
GLint m_locVertexInteger0; // "i0"
|
|
|
|
enum {
|
|
cMaxVertexShaderBoolUniforms = 4,
|
|
cMaxFragmentShaderBoolUniforms = 1
|
|
};
|
|
|
|
GLint m_locVertexBool[cMaxVertexShaderBoolUniforms]; // "b0", etc.
|
|
GLint m_locFragmentBool[cMaxFragmentShaderBoolUniforms]; // "fb0", etc.
|
|
bool m_bHasBoolOrIntUniforms;
|
|
|
|
// fragment stage uniforms
|
|
GLint m_locFragmentParams; // "pc" per dx9asmtogl2 convention
|
|
|
|
int m_NumUniformBufferParams[kGLMNumProgramTypes];
|
|
GLint m_UniformBufferParams[kGLMNumProgramTypes][256];
|
|
|
|
GLint m_locFragmentFakeSRGBEnable; // "flSRGBWrite" - set to 1.0 to effect
|
|
// sRGB encoding on output
|
|
float m_fakeSRGBEnableValue; // shadow to avoid redundant sets of the
|
|
// m_locFragmentFakeSRGBEnable uniform init it
|
|
// to -1.0 at link or relink, so it will trip
|
|
// on any legit incoming value (0.0 or 1.0)
|
|
|
|
GLint m_locSamplers[GLM_SAMPLER_COUNT]; // "sampler0 ... sampler1..."
|
|
|
|
// other stuff
|
|
bool m_valid; // true on successful link
|
|
bool m_bCheckLinkStatus;
|
|
uint m_revision; // if this pair is relinked, bump this number.
|
|
|
|
GLint m_locVertexScreenParams; // vcscreen
|
|
uint m_nScreenWidthHeight;
|
|
};
|
|
|
|
//===============================================================================
|
|
|
|
// N-row, M-way associative cache with LRU per row.
|
|
// still needs some metric dump ability and some parameter tuning.
|
|
// extra credit would be to make an auto-tuner.
|
|
|
|
struct CGLMPairCacheEntry {
|
|
long long m_lastMark; // a mark of zero means an empty entry
|
|
CGLMProgram *m_vertexProg;
|
|
CGLMProgram *m_fragmentProg;
|
|
uint m_extraKeyBits;
|
|
CGLMShaderPair *m_pair;
|
|
};
|
|
|
|
class CGLMShaderPairCache // cache for linked GLSL shader pairs
|
|
{
|
|
public:
|
|
protected:
|
|
friend class CGLMShaderPair;
|
|
friend class CGLMProgram;
|
|
friend class GLMContext;
|
|
|
|
//===============================
|
|
|
|
CGLMShaderPairCache(GLMContext *ctx);
|
|
~CGLMShaderPairCache();
|
|
|
|
FORCEINLINE CGLMShaderPair *SelectShaderPair(CGLMProgram *vp,
|
|
CGLMProgram *fp,
|
|
uint extraKeyBits);
|
|
void QueryShaderPair(int index, GLMShaderPairInfo *infoOut);
|
|
|
|
// shoot down linked pairs that use the program in the arg
|
|
// return true if any had to be skipped due to conflict with currently bound
|
|
// pair
|
|
bool PurgePairsWithShader(CGLMProgram *prog);
|
|
|
|
// purge everything (when would GLM know how to do this ? at context
|
|
// destroy time, but any other times?) return true if any had to be skipped
|
|
// due to conflict with currently bound pair
|
|
bool Purge(void);
|
|
|
|
// stats
|
|
void DumpStats(void);
|
|
|
|
//===============================
|
|
|
|
FORCEINLINE uint HashRowIndex(CGLMProgram *vp, CGLMProgram *fp,
|
|
uint extraKeyBits) const;
|
|
FORCEINLINE CGLMPairCacheEntry *HashRowPtr(uint hashRowIndex) const;
|
|
|
|
FORCEINLINE void HashRowProbe(CGLMPairCacheEntry *row, CGLMProgram *vp,
|
|
CGLMProgram *fp, uint extraKeyBits,
|
|
int &hitway, int &emptyway, int &oldestway);
|
|
|
|
CGLMShaderPair *SelectShaderPairInternal(CGLMProgram *vp, CGLMProgram *fp,
|
|
uint extraKeyBits, int rowIndex);
|
|
//===============================
|
|
|
|
// common stuff
|
|
|
|
GLMContext *m_ctx; // link back to parent context
|
|
|
|
long long m_mark;
|
|
|
|
uint m_rowsLg2;
|
|
uint m_rows;
|
|
uint m_rowsMask;
|
|
|
|
uint m_waysLg2;
|
|
uint m_ways;
|
|
|
|
uint m_entryCount;
|
|
|
|
CGLMPairCacheEntry *m_entries; // array[ m_rows ][ m_ways ]
|
|
|
|
uint *m_evictions; // array[ m_rows ];
|
|
|
|
#if GL_SHADER_PAIR_CACHE_STATS
|
|
uint *m_hits; // array[ m_rows ];
|
|
#endif
|
|
};
|
|
|
|
FORCEINLINE uint CGLMShaderPairCache::HashRowIndex(CGLMProgram *vp,
|
|
CGLMProgram *fp,
|
|
uint extraKeyBits) const {
|
|
return (vp->m_nHashTag + fp->m_nHashTag + extraKeyBits * 7) & m_rowsMask;
|
|
}
|
|
|
|
FORCEINLINE CGLMPairCacheEntry *CGLMShaderPairCache::HashRowPtr(
|
|
uint hashRowIndex) const {
|
|
return &m_entries[hashRowIndex * m_ways];
|
|
}
|
|
|
|
FORCEINLINE void CGLMShaderPairCache::HashRowProbe(
|
|
CGLMPairCacheEntry *row, CGLMProgram *vp, CGLMProgram *fp,
|
|
uint extraKeyBits, int &hitway, int &emptyway, int &oldestway) {
|
|
hitway = -1;
|
|
emptyway = -1;
|
|
oldestway = -1;
|
|
|
|
// scan this row to see if the desired pair is present
|
|
CGLMPairCacheEntry *cursor = row;
|
|
long long oldestmark = 0xFFFFFFFFFFFFFFFFLL;
|
|
|
|
for (uint way = 0; way < m_ways; ++way) {
|
|
if (cursor->m_lastMark != 0) // occupied slot
|
|
{
|
|
// check if this is the oldest one on the row - only occupied slots
|
|
// are checked
|
|
if (cursor->m_lastMark < oldestmark) {
|
|
oldestway = way;
|
|
oldestmark = cursor->m_lastMark;
|
|
}
|
|
|
|
if ((cursor->m_vertexProg == vp) &&
|
|
(cursor->m_fragmentProg == fp) &&
|
|
(cursor->m_extraKeyBits == extraKeyBits)) // match?
|
|
{
|
|
// found it
|
|
hitway = way;
|
|
break;
|
|
}
|
|
} else {
|
|
// empty way, log it if first one seen
|
|
if (emptyway < 0) {
|
|
emptyway = way;
|
|
}
|
|
}
|
|
cursor++;
|
|
}
|
|
}
|
|
|
|
FORCEINLINE CGLMShaderPair *CGLMShaderPairCache::SelectShaderPair(
|
|
CGLMProgram *vp, CGLMProgram *fp, uint extraKeyBits) {
|
|
// select row where pair would be found if it exists
|
|
uint rowIndex = HashRowIndex(vp, fp, extraKeyBits);
|
|
|
|
CGLMPairCacheEntry *pCursor = HashRowPtr(rowIndex);
|
|
|
|
if ((pCursor->m_fragmentProg != fp) || (pCursor->m_vertexProg != vp) ||
|
|
(pCursor->m_extraKeyBits != extraKeyBits)) {
|
|
CGLMPairCacheEntry *pLastCursor = pCursor + m_ways;
|
|
|
|
++pCursor;
|
|
|
|
while (pCursor != pLastCursor) {
|
|
if ((pCursor->m_fragmentProg == fp) &&
|
|
(pCursor->m_vertexProg == vp) &&
|
|
(pCursor->m_extraKeyBits == extraKeyBits)) // match?
|
|
break;
|
|
++pCursor;
|
|
};
|
|
|
|
if (pCursor == pLastCursor)
|
|
return SelectShaderPairInternal(vp, fp, extraKeyBits, rowIndex);
|
|
}
|
|
|
|
// found it. mark it and return
|
|
pCursor->m_lastMark = m_mark++;
|
|
|
|
#if GL_SHADER_PAIR_CACHE_STATS
|
|
// count the hit
|
|
m_hits[rowIndex]++;
|
|
#endif
|
|
|
|
return pCursor->m_pair;
|
|
}
|
|
|
|
#endif
|