diff --git a/binaries/data/config/system.cfg b/binaries/data/config/system.cfg index 1e3991024d..8189266817 100644 --- a/binaries/data/config/system.cfg +++ b/binaries/data/config/system.cfg @@ -27,6 +27,14 @@ novbo=false shadows=true vsync=false +; Specify the render path. This can be one of: +; default Automatically select one of the below, depending on system capabilities +; fixed Only use OpenGL fixed function pipeline +; vertexshader Use vertex shaders for transform and lighting where possible +; Using 'fixed' instead of 'default' may work around some graphics-related problems, +; but will reduce performance when a modern graphics card is available. +renderpath=default + ; Adjusts how OpenGL calculates mipmap level of detail. 0.0f is the default (blurry) value. ; Lower values sharpen/extend, and higher values blur/decrease. Clamped at -3.0 to 3.0. ; -1.0 to -1.5 recommended for good results. diff --git a/binaries/data/mods/official/shaders/model_light.vs b/binaries/data/mods/official/shaders/model_light.vs new file mode 100644 index 0000000000..b487e12dfe --- /dev/null +++ b/binaries/data/mods/official/shaders/model_light.vs @@ -0,0 +1,8 @@ +vec3 lighting(vec3 normal); + +void main() +{ + gl_FrontColor = gl_BackColor = vec4(lighting(gl_Normal),1.0) * gl_Color; + gl_TexCoord[0] = gl_MultiTexCoord0; + gl_Position = ftransform(); +} diff --git a/binaries/data/mods/official/shaders/model_light.xml b/binaries/data/mods/official/shaders/model_light.xml new file mode 100644 index 0000000000..a5059cff01 --- /dev/null +++ b/binaries/data/mods/official/shaders/model_light.xml @@ -0,0 +1,8 @@ + + + + + shaders/model_light.vs + shaders/shlight.vs + + diff --git a/binaries/data/mods/official/shaders/shlight.vs b/binaries/data/mods/official/shaders/shlight.vs new file mode 100644 index 0000000000..9b45a523f9 --- /dev/null +++ b/binaries/data/mods/official/shaders/shlight.vs @@ -0,0 +1,16 @@ +uniform vec3 SHCoefficients[9]; + +vec3 lighting(vec3 normal) +{ + vec3 color = SHCoefficients[0]; + vec3 normalsq = normal*normal; + color += SHCoefficients[1]*normal.x; + color += SHCoefficients[2]*normal.y; + color += SHCoefficients[3]*normal.z; + color += SHCoefficients[4]*(normal.x*normal.z); + color += SHCoefficients[5]*(normal.z*normal.y); + color += SHCoefficients[6]*(normal.y*normal.x); + color += SHCoefficients[7]*(3*normalsq.z-1); + color += SHCoefficients[8]*(normalsq.x-normalsq.y); + return color; +} diff --git a/source/lib/lib.h b/source/lib/lib.h index c0e427bb5a..c039b6ef89 100755 --- a/source/lib/lib.h +++ b/source/lib/lib.h @@ -273,6 +273,12 @@ enum LibError ERR_CPU_FEATURE_MISSING = -1600, + // shaders + ERR_SHDR_CREATE = -1700, + ERR_SHDR_COMPILE = -1701, + ERR_SHDR_NO_SHADER = -1702, + ERR_SHDR_LINK = -1703, + ERR_SHDR_NO_PROGRAM = -1704, ERR_LAST }; diff --git a/source/lib/res/graphics/ogl_shader.cpp b/source/lib/res/graphics/ogl_shader.cpp new file mode 100644 index 0000000000..2cd2224d1c --- /dev/null +++ b/source/lib/res/graphics/ogl_shader.cpp @@ -0,0 +1,425 @@ +#include "precompiled.h" + +#include "lib.h" +#include "../res.h" +#include "lib/ogl.h" + +#include "ps/CLogger.h" +#include "ps/XML/Xeromyces.h" + +#include "ogl_shader.h" + +#define LOG_CATEGORY "shaders" + + +// Convert a shader object type into a descriptive string. +// If the type enum is not known, the given buffer is used as scratch space +// to format the type number. If buf is null, a generic string is returned. +static const char* shader_type_to_string(GLenum type, char* buf, size_t buflen) +{ + switch(type) + { + case GL_VERTEX_SHADER_ARB: return "VERTEX_SHADER"; + case GL_FRAGMENT_SHADER_ARB: return "FRAGMENT_SHADER"; + } + + if (!buf) + return "unknown type enum"; + + snprintf(buf, buflen, "%u", type); + return buf; +} + +// Return the OpenGL shader type enum for the given string, +// or 0 if the shader type is not known. +static GLenum string_to_shader_type(const char* name) +{ + if (!stricmp(name, "VERTEX_SHADER")) + return GL_VERTEX_SHADER_ARB; + if (!stricmp(name, "FRAGMENT_SHADER")) + return GL_FRAGMENT_SHADER_ARB; + return 0; +} + + +//---------------------------------------------------------------------------- +// Handle type implementation + +// Data for an Ogl_Shader object +struct Ogl_Shader { + // Type of shader (e.g. GL_VERTEX_SHADER_ARB) + GLenum type; + + // ID of the OpenGL shader object + GLhandleARB id; +}; + + +H_TYPE_DEFINE(Ogl_Shader); + + +// One-time initialization, called once by h_alloc, which is +// in turn called by ogl_shader_load +static void Ogl_Shader_init(Ogl_Shader* shdr, va_list args) +{ + shdr->type = va_arg(args, GLenum); +} + + +// Reload the shader object from the source file. +// +// TODO: The OpenGL specification says that all changes to shader objects +// have absolutely no effect on a program object that contains these shaders +// when the program object is already linked. +// So, how can we inform the "parent object" (i.e. the program object) of our change? +static int Ogl_Shader_reload(Ogl_Shader* shdr, const char* filename, Handle h) +{ + int err = -666; + + if (shdr->id) + return 0; + + void* file; + size_t file_size; + GLint log_length; + GLint compile_success; + Handle hm; + + hm = vfs_load(filename, file, file_size); + RETURN_ERR(hm); + + oglCheck(); + + shdr->id = glCreateShaderObjectARB(shdr->type); + if (!shdr->id) + { + // May be out of memory, but bad shdr->type is also possible. + // In any case, checking OpenGL error state will help spot + // bad code. + oglCheck(); + + err = ERR_SHDR_CREATE; + goto fail_fileloaded; + } + + glShaderSourceARB(shdr->id, 1, (const char**)&file, (const GLint*)&file_size); + glCompileShaderARB(shdr->id); + + glGetObjectParameterivARB(shdr->id, GL_OBJECT_COMPILE_STATUS_ARB, &compile_success); + glGetObjectParameterivARB(shdr->id, GL_OBJECT_INFO_LOG_LENGTH_ARB, &log_length); + if (log_length > 1) + { + char typenamebuf[32]; + char* infolog = new char[log_length]; + + glGetInfoLogARB(shdr->id, log_length, 0, infolog); + + debug_printf("Compile log for shader %hs (type %hs):\n%hs", + filename, + shader_type_to_string(shdr->type, typenamebuf, ARRAY_SIZE(typenamebuf)), + infolog); + + delete[] infolog; + } + + if (!compile_success) + { + // Compilation failure caused by syntax errors and similar + // errors at the GLSL level does not set OpenGL error state + // according to the spec, but this might still prove to be + // useful some time. + oglCheck(); + + char typenamebuf[32]; + debug_printf("Failed to compile shader %hs (type %hs)\n", + filename, + shader_type_to_string(shdr->type, typenamebuf, ARRAY_SIZE(typenamebuf))); + + err = ERR_SHDR_COMPILE; + goto fail_shadercreated; + } + + mem_free_h(hm); + return 0; + +fail_shadercreated: + glDeleteObjectARB(shdr->id); + shdr->id = 0; +fail_fileloaded: + mem_free_h(hm); + return err; +} + + +// Free associated resources +static void Ogl_Shader_dtor(Ogl_Shader* shdr) +{ + // shdr->id is 0 when reload has failed + if (shdr->id) + { + glDeleteObjectARB(shdr->id); + shdr->id = 0; + } +} + + + +//---------------------------------------------------------------------------- +// Public API + +// Create, load and compile a shader object of the given type +// (e.g. GL_VERTEX_SHADER_ARB). The given file will be used as +// source code for the shader. +Handle ogl_shader_load(const char* fn, GLenum type) +{ + return h_alloc(H_Ogl_Shader, fn, 0, type); +} + + +// Free all resources associated with the given handle (subject +// to refcounting). +void ogl_shader_free(Handle& h) +{ + h_free(h, H_Ogl_Shader); +} + +// Attach a shader to the given OpenGL program. +int ogl_shader_attach(GLhandleARB program, Handle& h) +{ + H_DEREF(h, Ogl_Shader, shdr); + + if (!shdr->id) + return ERR_SHDR_NO_SHADER; + + glAttachObjectARB(program, shdr->id); + + return 0; +} + + + +//---------------------------------------------------------------------------- +// Program type implementation + +struct Ogl_Program { + // ID of the OpenGL program object + GLhandleARB id; +}; + + +H_TYPE_DEFINE(Ogl_Program); + + +// One-time initialization, called once by h_alloc, which is +// in turn called by ogl_program_load +static void Ogl_Program_init(Ogl_Program* p, va_list args) +{ +} + + +// Load the shader associated with one Shader element, +// and attach it to our program object. +static int do_load_shader( + Ogl_Program* p, const char* filename, Handle h, + const CXeromyces& XeroFile, const XMBElement& Shader) +{ +#define AT(x) int at_##x = XeroFile.getAttributeID(#x) + AT(type); +#undef AT + + CStr Type = Shader.getAttributes().getNamedItem(at_type); + + if (!Type.Length()) + { + LOG(ERROR, LOG_CATEGORY, "%hs: Missing attribute \"type\" in element \"Shader\".", + filename); + return ERR_CORRUPTED; + } + + GLenum shadertype = string_to_shader_type(Type.c_str()); + + if (!shadertype) + { + LOG(ERROR, LOG_CATEGORY, "%hs: Unknown shader type \"%hs\" (valid are: VERTEX_SHADER, FRAGMENT_SHADER).", + filename, Type.c_str()); + return ERR_CORRUPTED; + } + + CStr Name = Shader.getText(); + + if (!Name.Length()) + { + LOG(ERROR, LOG_CATEGORY, "%hs: Missing shader name.", filename); + return ERR_CORRUPTED; + } + + Handle hshader = ogl_shader_load(Name.c_str(), shadertype); + RETURN_ERR(hshader); + + ogl_shader_attach(p->id, hshader); + + // According to the OpenGL specification, a shader object's deletion + // will not be final as long as the shader object is attached to a + // container object. + // TODO: How will this work with automatic reload? + ogl_shader_free(hshader); + + return 0; +} + + +// Reload the program object from the source file. +static int Ogl_Program_reload(Ogl_Program* p, const char* filename, Handle h) +{ + if (p->id) + return 0; + + oglCheck(); + + p->id = glCreateProgramObjectARB(); + if (!p->id) + { + // The spec doesn't mention any error state that can be set + // here, but it may still help spot bad code. + oglCheck(); + + return ERR_SHDR_CREATE; + } + + CXeromyces XeroFile; + if (XeroFile.Load(filename) != PSRETURN_OK) + return ERR_CORRUPTED; // more informative error message? + + // Define all the elements and attributes used in the XML file +#define EL(x) int el_##x = XeroFile.getElementID(#x) + EL(program); + EL(shaders); + EL(shader); +#undef EL + + XMBElement Root = XeroFile.getRoot(); + + if (Root.getNodeName() != el_program) + { + LOG(ERROR, LOG_CATEGORY, "%hs: XML root was not \"Program\".", filename); + return ERR_CORRUPTED; + } + + XMBElementList RootChildren = Root.getChildNodes(); + + for(int i = 0; i < RootChildren.Count; ++i) + { + XMBElement Child = RootChildren.item(i); + + int ChildName = Child.getNodeName(); + if (ChildName == el_shaders) + { + XMBElementList Shaders = Child.getChildNodes(); + + for(int j = 0; j < Shaders.Count; ++j) + { + XMBElement Shader = Shaders.item(j); + + if (Shader.getNodeName() != el_shader) + { + LOG(ERROR, LOG_CATEGORY, "%hs: Only \"Shader\" may be child of \"Shaders\".", + filename); + return ERR_CORRUPTED; + } + + int ret = do_load_shader(p, filename, h, XeroFile, Shader); + if (ret < 0) + return ret; + } + } + else + { + LOG(WARNING, LOG_CATEGORY, "%hs: Unknown child of \"Program\".", filename); + } + } + + glLinkProgramARB(p->id); + + GLint log_length; + GLint linked; + + glGetObjectParameterivARB(p->id, GL_OBJECT_LINK_STATUS_ARB, &linked); + glGetObjectParameterivARB(p->id, GL_OBJECT_INFO_LOG_LENGTH_ARB, &log_length); + if (log_length > 1) + { + char* infolog = new char[log_length]; + glGetInfoLogARB(p->id, log_length, 0, infolog); + + debug_printf("Linker log for %hs:\n%hs\n", filename, infolog); + delete[] infolog; + } + + if (!linked) + { + debug_printf("Link failed for %hs\n", filename); + return ERR_SHDR_LINK; + } + + return 0; +} + + +// Free associated resources +static void Ogl_Program_dtor(Ogl_Program* p) +{ + if (p->id) + { + glDeleteObjectARB(p->id); + p->id = 0; + } +} + + + +//---------------------------------------------------------------------------- +// Public API + +// Load a program object based on the given XML file description. +// Shader objects are loaded and attached automatically. +Handle ogl_program_load(const char* fn) +{ + return h_alloc(H_Ogl_Program, fn, 0); +} + +// Free all resources associated with the given program handle. +void ogl_program_free(Handle& h) +{ + h_free(h, H_Ogl_Program); +} + + +// Activate the program (glUseProgramObjectARB). +// h may be 0, in which case program objects are disabled. +int ogl_program_use(Handle h) +{ + if (!h) + { + glUseProgramObjectARB(0); + return 0; + } + + Ogl_Program* p = H_USER_DATA(h, Ogl_Program); + if (!p || !p->id) + { + glUseProgramObjectARB(0); + CHECK_ERR(ERR_INVALID_HANDLE); + UNREACHABLE; + } + + glUseProgramObjectARB(p->id); + return 0; +} + + +// Query uniform information +GLint ogl_program_get_uniform_location(Handle h, const char* name) +{ + H_DEREF(h, Ogl_Program, p); + + return glGetUniformLocationARB(p->id, name); +} diff --git a/source/lib/res/graphics/ogl_shader.h b/source/lib/res/graphics/ogl_shader.h new file mode 100644 index 0000000000..4cb9c6cee9 --- /dev/null +++ b/source/lib/res/graphics/ogl_shader.h @@ -0,0 +1,50 @@ +#ifndef OGL_SHADER_H__ +#define OGL_SHADER_H__ + +#include "../handle.h" + +#include "lib/types.h" +#include "lib/ogl.h" + +/* +Encapsulate shader objects into handles, which transparently enables sharing +of shader source files between programs as well as reloading shaders at +runtime. + +NOTE: Only use functions form this module after verifying that the required +extensions are available, or all bets are off. +*/ + +// Create, load and compile a shader object of the given type +// (e.g. GL_VERTEX_SHADER_ARB). The given file will be used as +// source code for the shader. +Handle ogl_shader_load(const char* fn, GLenum type); + +// Free all resources associated with the given handle (subject +// to refcounting). +void ogl_shader_free(Handle& h); + +// Attach a shader to the given OpenGL program. +// Returns 0 on success and a negative error code otherwise. +int ogl_shader_attach(GLhandleARB program, Handle& h); + + +/* +Encapsulate program objects into handles. +*/ + +// Load a program object based on the given XML file description. +// Shader objects are loaded and attached automatically. +Handle ogl_program_load(const char* fn); + +// Free all resources associated with the given program handle. +void ogl_program_free(Handle& h); + +// Activate the program (glUseProgramObjectARB). +// h may be 0, in which case program objects are disabled. +int ogl_program_use(Handle h); + +// Query uniform information +GLint ogl_program_get_uniform_location(Handle h, const char* name); + +#endif // OGL_SHADER_H__ diff --git a/source/ps/GameSetup/Config.cpp b/source/ps/GameSetup/Config.cpp index 59ef72e3dc..0ea0a9aa92 100644 --- a/source/ps/GameSetup/Config.cpp +++ b/source/ps/GameSetup/Config.cpp @@ -26,6 +26,7 @@ bool g_VSync = false; float g_LodBias = 0.0f; float g_Gamma = 1.0f; bool g_EntGraph = false; +CStr g_RenderPath = "default"; // graphics mode int g_xres, g_yres; @@ -67,6 +68,7 @@ static void LoadGlobals() CFG_GET_USER_VAL("vsync", Bool, g_VSync); CFG_GET_USER_VAL("novbo", Bool, g_NoGLVBO); CFG_GET_USER_VAL("shadows", Bool, g_Shadows); + CFG_GET_USER_VAL("renderpath", String, g_RenderPath); CFG_GET_USER_VAL("lodbias", Float, g_LodBias); diff --git a/source/ps/GameSetup/Config.h b/source/ps/GameSetup/Config.h index 0485fc9e13..85273fae71 100644 --- a/source/ps/GameSetup/Config.h +++ b/source/ps/GameSetup/Config.h @@ -13,6 +13,9 @@ extern bool g_VSync; extern float g_LodBias; extern float g_Gamma; extern bool g_EntGraph; +// name of configured render path (depending on OpenGL extensions, this may not be +// the render path that is actually in use right now) +extern CStr g_RenderPath; extern int g_xres, g_yres; diff --git a/source/ps/GameSetup/GameSetup.cpp b/source/ps/GameSetup/GameSetup.cpp index 09114c08dd..fa880cd2e0 100644 --- a/source/ps/GameSetup/GameSetup.cpp +++ b/source/ps/GameSetup/GameSetup.cpp @@ -543,6 +543,7 @@ static void InitRenderer() g_Renderer.SetOptionBool(CRenderer::OPT_NOVBO,g_NoGLVBO); g_Renderer.SetOptionBool(CRenderer::OPT_SHADOWS,g_Shadows); g_Renderer.SetOptionBool(CRenderer::OPT_NOPBUFFER,g_NoPBuffer); + g_Renderer.SetRenderPath(CRenderer::GetRenderPathByName(g_RenderPath)); g_Renderer.SetOptionFloat(CRenderer::OPT_LODBIAS, g_LodBias); // create terrain related stuff diff --git a/source/renderer/ModelDefRData.cpp b/source/renderer/ModelDefRData.cpp index c4437dbcec..2357e5b385 100644 --- a/source/renderer/ModelDefRData.cpp +++ b/source/renderer/ModelDefRData.cpp @@ -32,34 +32,42 @@ CModelDefRData::~CModelDefRData() // Create and upload shared vertex arrays +// +// UV is only shared for fixed function at the moment. +// Rationale: The non-shared vertex structure has enough space left for UV +// coordinates due to alignment, so we slightly *reduce* vertex buffer space by +// not sharing UV. void CModelDefRData::Build() { - size_t numVertices = m_ModelDef->GetNumVertices(); - - m_UV.type = GL_FLOAT; - m_UV.elems = 2; - m_Array.AddAttribute(&m_UV); + if (g_Renderer.GetRenderPath() == CRenderer::RP_FIXED) + { + size_t numVertices = m_ModelDef->GetNumVertices(); - m_Array.SetNumVertices(numVertices); - m_Array.Layout(); + m_UV.type = GL_FLOAT; + m_UV.elems = 2; + m_Array.AddAttribute(&m_UV); + + m_Array.SetNumVertices(numVertices); + m_Array.Layout(); + + SModelVertex* vertices = m_ModelDef->GetVertices(); + VertexArrayIterator UVit = m_UV.GetIterator(); + + for (uint j=0; j < numVertices; ++j, ++UVit) { + (*UVit)[0] = vertices[j].m_U; + (*UVit)[1] = 1.0-vertices[j].m_V; + } - SModelVertex* vertices = m_ModelDef->GetVertices(); - VertexArrayIterator UVit = m_UV.GetIterator(); - - for (uint j=0; j < numVertices; ++j, ++UVit) { - (*UVit)[0] = vertices[j].m_U; - (*UVit)[1] = 1.0-vertices[j].m_V; + m_Array.Upload(); + m_Array.FreeBackingStore(); } - - m_Array.Upload(); - m_Array.FreeBackingStore(); } // Setup shared vertex arrays as needed. void CModelDefRData::PrepareStream(uint streamflags) { - if (!(streamflags & STREAM_UV0)) + if (!(streamflags & STREAM_UV0) || !m_UV.type) return; u8* base = m_Array.Bind(); diff --git a/source/renderer/ModelDefRData.h b/source/renderer/ModelDefRData.h index 0203a64243..dd1c50ea55 100644 --- a/source/renderer/ModelDefRData.h +++ b/source/renderer/ModelDefRData.h @@ -34,7 +34,7 @@ private: CModelDef* m_ModelDef; VertexArray m_Array; - VertexArray::Attribute m_UV; + VertexArray::Attribute m_UV; // only used in RP_FIXED CModelDefRData* m_SubmissionNext; uint m_SubmissionSlots; diff --git a/source/renderer/ModelRData.cpp b/source/renderer/ModelRData.cpp index 28b0b44215..6b1613f1f1 100755 --- a/source/renderer/ModelRData.cpp +++ b/source/renderer/ModelRData.cpp @@ -4,6 +4,7 @@ #include #include "MathUtil.h" #include "lib/res/graphics/ogl_tex.h" +#include "lib/res/graphics/ogl_shader.h" #include "Renderer.h" #include "TransparencyRenderer.h" #include "PlayerRenderer.h" @@ -13,13 +14,14 @@ #include "MaterialManager.h" #include "Profile.h" #include "renderer/ModelDefRData.h" +#include "renderer/RenderPathVertexShader.h" /////////////////////////////////////////////////////////////////// // CModelRData constructor CModelRData::CModelRData(CModel* model) - : m_Model(model), m_Normals(0), m_DynamicArray(true), m_Indices(0), m_Flags(0) + : m_Model(model), m_TempNormals(0), m_DynamicArray(true), m_Indices(0), m_Flags(0) { debug_assert(model); // build all data now @@ -32,7 +34,7 @@ CModelRData::~CModelRData() { // clean up system copies of data delete[] m_Indices; - delete[] m_Normals; + delete[] m_TempNormals; } void CModelRData::Build() @@ -47,14 +49,23 @@ void CModelRData::Build() m_Position.type = GL_FLOAT; m_Position.elems = 3; m_DynamicArray.AddAttribute(&m_Position); -/* - m_UV.type = GL_FLOAT; - m_UV.elems = 2; - m_DynamicArray.AddAttribute(&m_UV); -*/ - m_Color.type = GL_UNSIGNED_BYTE; - m_Color.elems = 3; - m_DynamicArray.AddAttribute(&m_Color); + + if (g_Renderer.GetRenderPath() == CRenderer::RP_VERTEXSHADER) + { + m_UV.type = GL_FLOAT; + m_UV.elems = 2; + m_DynamicArray.AddAttribute(&m_UV); + + m_Normal.type = GL_FLOAT; + m_Normal.elems = 3; + m_DynamicArray.AddAttribute(&m_Normal); + } + else + { + m_Color.type = GL_UNSIGNED_BYTE; + m_Color.elems = 3; + m_DynamicArray.AddAttribute(&m_Color); + } m_DynamicArray.SetNumVertices(mdef->GetNumVertices()); m_DynamicArray.Layout(); @@ -146,17 +157,18 @@ static void SkinNormal(const SModelVertex& vertex,const CMatrix3D* invmatrices,C void CModelRData::BuildStaticVertices() { -/* - CModelDefPtr mdef = m_Model->GetModelDef(); - size_t numVertices = mdef->GetNumVertices(); - SModelVertex* vertices = mdef->GetVertices(); - VertexArrayIterator UVit = m_UV.GetIterator(); - - for (uint j=0; j < numVertices; ++j, ++UVit) { - (*UVit)[0] = vertices[j].m_U; - (*UVit)[1] = 1.0-vertices[j].m_V; + if (m_UV.type) + { + CModelDefPtr mdef = m_Model->GetModelDef(); + size_t numVertices = mdef->GetNumVertices(); + SModelVertex* vertices = mdef->GetVertices(); + VertexArrayIterator UVit = m_UV.GetIterator(); + + for (uint j=0; j < numVertices; ++j, ++UVit) { + (*UVit)[0] = vertices[j].m_U; + (*UVit)[1] = 1.0-vertices[j].m_V; + } } -*/ } void CModelRData::BuildVertices() @@ -165,14 +177,21 @@ void CModelRData::BuildVertices() size_t numVertices=mdef->GetNumVertices(); SModelVertex* vertices=mdef->GetVertices(); - // allocate vertices if we haven't got any already and - // fill in data that never changes - if (!m_Normals) - m_Normals=new CVector3D[mdef->GetNumVertices()]; - // build vertices VertexArrayIterator Position = m_Position.GetIterator(); - VertexArrayIterator Color = m_Color.GetIterator(); + VertexArrayIterator Normal; + + if (m_Normal.type) + { + Normal = m_Normal.GetIterator(); + } + else + { + if (!m_TempNormals) + m_TempNormals = new CVector3D[numVertices]; + Normal = VertexArrayIterator((char*)m_TempNormals, sizeof(CVector3D)); + } + const CMatrix3D* bonematrices=m_Model->GetBoneMatrices(); if (bonematrices) { // boned model - calculate skinned vertex positions/normals @@ -180,35 +199,90 @@ void CModelRData::BuildVertices() const CMatrix3D* invbonematrices=m_Model->GetInvBoneMatrices(); for (size_t j=0; jGetTransform(); const CMatrix3D& invtransform=m_Model->GetInvTransform(); for (uint j=0; jGetShadingColor(); - RGBColor shadingcolor(sc.r, sc.g, sc.b); - RGBColor tempcolor; - for (uint j=0; j Color = m_Color.GetIterator(); + CSHCoeffs& shcoeffs = g_Renderer.m_SHCoeffsUnits; + CColor sc = m_Model->GetShadingColor(); + RGBColor shadingcolor(sc.r, sc.g, sc.b); + RGBColor tempcolor; + for (uint j=0; jm_ModelLight); + idx = g_Renderer.m_VertexShader->m_ModelLight_SHCoefficients; + glUniform3fvARB(idx, 9, (float*)coeffs); + + glEnableClientState(GL_NORMAL_ARRAY); + } + else + { + glEnableClientState(GL_COLOR_ARRAY); + } + } +} + +// reset state prepared by SetupRender +void CModelRData::FinishRender(u32 streamflags) +{ + glDisableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + + if (streamflags & STREAM_COLOR) + { + if (g_Renderer.GetRenderPath() == CRenderer::RP_VERTEXSHADER) + { + glUseProgramObjectARB(0); + + glDisableClientState(GL_NORMAL_ARRAY); + } + else + { + glDisableClientState(GL_COLOR_ARRAY); + } + } +} + + +// Render one indiviual model. +// Try to use RenderModels instead wherever possible. +// Must be bracketed by calls to CModelRData::SetupRender/FinishRender void CModelRData::RenderStreams(u32 streamflags, bool isplayer) { CModelDefPtr mdldef=m_Model->GetModelDef(); @@ -223,24 +297,29 @@ void CModelRData::RenderStreams(u32 streamflags, bool isplayer) g_Renderer.SetTexture(0,m_Model->GetTexture()); } - ((CModelDefRData*)mdldef->GetRenderData())->PrepareStream(streamflags); - u8* base = m_DynamicArray.Bind(); size_t stride = m_DynamicArray.GetStride(); glVertexPointer(3, GL_FLOAT, stride, base + m_Position.offset); - if (streamflags & STREAM_COLOR) glColorPointer(3, m_Color.type, stride, base + m_Color.offset); -#if 0 + if (streamflags & STREAM_COLOR) { - uint a = (uint)this; - uint b = (uint)m_Model->GetTexture(); - uint hash = ((a >> 16) ^ (b & 0xffff)) | ((a & 0xffff0000) ^ (b << 16)); - hash = (hash * 65537) + 17; - hash |= 0xff000000; - glColor4ubv((GLubyte*)&hash); - glDisableClientState(GL_COLOR_ARRAY); + if (m_Normal.type) + { + CColor sc = m_Model->GetShadingColor(); + glColor3f(sc.r, sc.g, sc.b); + + glNormalPointer(GL_FLOAT, stride, base + m_Normal.offset); + } + else + glColorPointer(3, m_Color.type, stride, base + m_Color.offset); + } + if (streamflags & STREAM_UV0) + { + if (m_UV.type) + glTexCoordPointer(2, GL_FLOAT, stride, base + m_UV.offset); + else + ((CModelDefRData*)mdldef->GetRenderData())->PrepareStream(streamflags); } -#endif // render the lot size_t numFaces=mdldef->GetNumFaces(); @@ -331,6 +410,7 @@ float CModelRData::BackToFrontIndexSort(CMatrix3D& objToCam) ///////////////////////////////////////////////////////////////////////////////////////////////////// // RenderModels: render all submitted models; assumes necessary client states already enabled, // and texture environment already setup as required +// Must be bracketed by calls to CModelRData::SetupRender/FinishRender void CModelRData::RenderModels(u32 streamflags, u32 flags) { for(CModelDefRData* mdefdata = CModelDefRData::m_Submissions; @@ -359,9 +439,27 @@ void CModelRData::RenderModels(u32 streamflags, u32 flags) glVertexPointer(3, GL_FLOAT, stride, base + modeldata->m_Position.offset); if (streamflags & STREAM_COLOR) - glColorPointer(3, modeldata->m_Color.type, stride, - base + modeldata->m_Color.offset); - + { + if (modeldata->m_Normal.type) + { + CColor sc = modeldata->GetModel()->GetShadingColor(); + + glColor3f(sc.r, sc.g, sc.b); + + glNormalPointer(GL_FLOAT, stride, base + modeldata->m_Normal.offset); + } + else + { + glColorPointer(3, modeldata->m_Color.type, stride, + base + modeldata->m_Color.offset); + } + } + if (streamflags & STREAM_UV0 && modeldata->m_UV.type) + { + glTexCoordPointer(2, GL_FLOAT, stride, + base + modeldata->m_UV.offset); + } + // render the lot size_t numFaces=mdldef->GetNumFaces(); glDrawRangeElementsEXT(GL_TRIANGLES, 0, mdldef->GetNumVertices(), diff --git a/source/renderer/ModelRData.h b/source/renderer/ModelRData.h index 2faae32d98..bdd83f419f 100755 --- a/source/renderer/ModelRData.h +++ b/source/renderer/ModelRData.h @@ -38,6 +38,11 @@ public: // clear per frame model list static void ClearSubmissions(); + // prepare for rendering of models + static void SetupRender(u32 streamflags); + // reset state prepared by SetupRender + static void FinishRender(u32 streamflags); + // render all submitted models static void RenderModels(u32 streamflags,u32 flags=0); @@ -52,12 +57,14 @@ private: // owner model CModel* m_Model; // transformed vertex normals - required for recalculating lighting on skinned models - CVector3D* m_Normals; + // only used in render path RP_FIXED + CVector3D* m_TempNormals; // vertex array VertexArray m_DynamicArray; VertexArray::Attribute m_Position; - VertexArray::Attribute m_UV; - VertexArray::Attribute m_Color; + VertexArray::Attribute m_UV; // only used in RP_VERTEXSHADER (shared otherwise) + VertexArray::Attribute m_Color; // only used in RP_FIXED + VertexArray::Attribute m_Normal; // only used in RP_VERTEXSHADER // model render indices u16* m_Indices; // model render flags diff --git a/source/renderer/PlayerRenderer.cpp b/source/renderer/PlayerRenderer.cpp index 941dd483a1..25f7f280e8 100644 --- a/source/renderer/PlayerRenderer.cpp +++ b/source/renderer/PlayerRenderer.cpp @@ -89,9 +89,7 @@ void CPlayerRenderer::Render() // supports register combiners / fragment programs / etc (since it // would only need a single pass and no blending) - glEnableClientState(GL_VERTEX_ARRAY); - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - glEnableClientState(GL_COLOR_ARRAY); + CModelRData::SetupRender(STREAM_POS|STREAM_COLOR|STREAM_UV0); glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_CONSTANT); @@ -122,10 +120,7 @@ void CPlayerRenderer::Render() glActiveTextureARB(GL_TEXTURE0); - // switch off client states - glDisableClientState(GL_TEXTURE_COORD_ARRAY); - glDisableClientState(GL_VERTEX_ARRAY); - glDisableClientState(GL_COLOR_ARRAY); + CModelRData::FinishRender(STREAM_POS|STREAM_COLOR|STREAM_UV0); if (g_Renderer.m_ModelRenderMode==WIREFRAME) { // switch wireframe off again @@ -144,14 +139,10 @@ void CPlayerRenderer::Render() glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); - // .. and some client states - glEnableClientState(GL_VERTEX_ARRAY); - // render each model + CModelRData::SetupRender(STREAM_POS); RenderObjectsStreams(STREAM_POS); - - // .. and switch off the client states - glDisableClientState(GL_VERTEX_ARRAY); + CModelRData::FinishRender(STREAM_POS); // .. and restore the renderstates glDisable(GL_BLEND); @@ -211,4 +202,4 @@ void CPlayerRenderer::RenderObjectsStreams(u32 streamflags, bool iscolorpass, u3 modeldata->RenderStreams(streamflags, iscolorpass); } } -} +} diff --git a/source/renderer/RenderPathVertexShader.cpp b/source/renderer/RenderPathVertexShader.cpp new file mode 100644 index 0000000000..0727e3ab98 --- /dev/null +++ b/source/renderer/RenderPathVertexShader.cpp @@ -0,0 +1,48 @@ +#include "precompiled.h" + +#include "ogl.h" +#include "lib/res/graphics/ogl_shader.h" +#include "ps/CLogger.h" +#include "renderer/Renderer.h" +#include "renderer/RenderPathVertexShader.h" + +#define LOG_CATEGORY "graphics" + + +RenderPathVertexShader::RenderPathVertexShader() +{ + m_ModelLight = 0; + m_ModelLight_SHCoefficients = -1; +} + +RenderPathVertexShader::~RenderPathVertexShader() +{ + if (m_ModelLight) + ogl_program_free(m_ModelLight); +} + +// Initialize this render path. +// Use delayed initialization so that we can fallback to a different render path +// if anything went wrong and use the destructor to clean things up. +bool RenderPathVertexShader::Init() +{ + if (!g_Renderer.m_Caps.m_VertexShader) + return false; + + m_ModelLight = ogl_program_load("shaders/model_light.xml"); + if (m_ModelLight < 0) + { + LOG(WARNING, LOG_CATEGORY, "Failed to load shaders/model_light.xml: %i\n", (int)m_ModelLight); + return false; + } + + return true; +} + + +// This is quite the hack, but due to shader reloads, +// the uniform locations might have changed under us. +void RenderPathVertexShader::BeginFrame() +{ + m_ModelLight_SHCoefficients = ogl_program_get_uniform_location(m_ModelLight, "SHCoefficients"); +} diff --git a/source/renderer/RenderPathVertexShader.h b/source/renderer/RenderPathVertexShader.h new file mode 100644 index 0000000000..2aa3460868 --- /dev/null +++ b/source/renderer/RenderPathVertexShader.h @@ -0,0 +1,21 @@ +#ifndef __RENDERPATHVERTEXSHADER_H__ +#define __RENDERPATHVERTEXSHADER_H__ + +class RenderPathVertexShader +{ +public: + RenderPathVertexShader(); + ~RenderPathVertexShader(); + + // Initialize this render path. + bool Init(); + + // Call once per frame to update program stuff + void BeginFrame(); + +public: + Handle m_ModelLight; + GLint m_ModelLight_SHCoefficients; +}; + +#endif // __RENDERPATHVERTEXSHADER_H__ diff --git a/source/renderer/Renderer.cpp b/source/renderer/Renderer.cpp index 13b88ad858..ff153cefef 100755 --- a/source/renderer/Renderer.cpp +++ b/source/renderer/Renderer.cpp @@ -41,6 +41,8 @@ #include "lib/res/graphics/ogl_tex.h" #include "timer.h" +#include "renderer/RenderPathVertexShader.h" + #define LOG_CATEGORY "graphics" /////////////////////////////////////////////////////////////////////////////////// @@ -56,9 +58,12 @@ CRenderer::CRenderer() m_ClearColor[0]=m_ClearColor[1]=m_ClearColor[2]=m_ClearColor[3]=0; m_ShadowMap=0; + m_VertexShader = 0; + m_Options.m_NoVBO=false; m_Options.m_Shadows=true; m_Options.m_ShadowColor=RGBAColor(0.4f,0.4f,0.4f,1.0f); + m_Options.m_RenderPath = RP_DEFAULT; for (uint i=0;iInit()) + { + delete m_VertexShader; + m_VertexShader = 0; + m_Options.m_RenderPath = RP_FIXED; + } + } + + LOG(NORMAL, LOG_CATEGORY, "Selected render path: %hs (configuration value: %hs)", + GetRenderPathName(m_Options.m_RenderPath).c_str(), + GetRenderPathName(desired).c_str()); +} + bool CRenderer::Open(int width, int height, int depth) { m_Width = width; @@ -160,11 +190,15 @@ bool CRenderer::Open(int width, int height, int depth) glGetIntegerv(GL_ALPHA_BITS,&bits); LOG(NORMAL, LOG_CATEGORY, "CRenderer::Open: alpha bits %d",bits); + InitRenderPath(); + return true; } void CRenderer::Close() { + delete m_VertexShader; + m_VertexShader = 0; } // resize renderer view @@ -241,6 +275,47 @@ const RGBAColor& CRenderer::GetOptionColor(enum Option opt) const return defaultColor; } + +////////////////////////////////////////////////////////////////////////////////////////// +// SetRenderPath: Select the preferred render path. +// This may only be called before Open(), because the layout of vertex arrays and other +// data may depend on the chosen render path. +void CRenderer::SetRenderPath(RenderPath rp) +{ + if (m_Options.m_RenderPath != RP_DEFAULT && rp != m_Options.m_RenderPath) + { + debug_warn("Cannot change RenderPath after the fact"); + return; + } + + m_Options.m_RenderPath = rp; +} + + +CStr CRenderer::GetRenderPathName(RenderPath rp) +{ + switch(rp) { + case RP_DEFAULT: return "default"; + case RP_FIXED: return "fixed"; + case RP_VERTEXSHADER: return "vertexshader"; + default: return "(invalid)"; + } +} + +CRenderer::RenderPath CRenderer::GetRenderPathByName(CStr name) +{ + if (name == "fixed") + return RP_FIXED; + if (name == "vertexshader") + return RP_VERTEXSHADER; + if (name == "default") + return RP_DEFAULT; + + LOG(WARNING, LOG_CATEGORY, "Unknown render path name '%hs', assuming 'default'", name.c_str()); + return RP_DEFAULT; +} + + ////////////////////////////////////////////////////////////////////////////////////////// // BeginFrame: signal frame start void CRenderer::BeginFrame() @@ -253,9 +328,12 @@ void CRenderer::BeginFrame() // bump frame counter m_FrameCounter++; + if (m_VertexShader) + m_VertexShader->BeginFrame(); + // zero out all the per-frame stats m_Stats.Reset(); - + // calculate coefficients for terrain and unit lighting m_SHCoeffsUnits.Clear(); m_SHCoeffsTerrain.Clear(); @@ -628,9 +706,6 @@ void CRenderer::RenderShadowMap() glEnd(); #endif // 0 - // setup client states - glEnableClientState(GL_VERTEX_ARRAY); - glEnable(GL_SCISSOR_TEST); glScissor(1,1,m_Width-2,m_Height-2); @@ -650,6 +725,8 @@ void CRenderer::RenderShadowMap() glDisable(GL_CULL_FACE); + CModelRData::SetupRender(STREAM_POS); + // render models CModelRData::RenderModels(STREAM_POS,MODELFLAG_CASTSHADOWS); @@ -659,12 +736,13 @@ void CRenderer::RenderShadowMap() // call on the transparency renderer to render all the transparent stuff g_TransparencyRenderer.RenderShadows(); + CModelRData::FinishRender(STREAM_POS); + glEnable(GL_CULL_FACE); glColor3f(1.0f,1.0f,1.0f); glDisable(GL_SCISSOR_TEST); - glDisableClientState(GL_VERTEX_ARRAY); // copy result into shadow map texture BindTexture(0,m_ShadowMap); @@ -938,18 +1016,10 @@ void CRenderer::RenderModelSubmissions() float color[] = { 1.0, 1.0, 1.0, 1.0 }; glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, color); - // setup client states - glEnableClientState(GL_VERTEX_ARRAY); - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - glEnableClientState(GL_COLOR_ARRAY); - // render models + CModelRData::SetupRender(STREAM_POS|STREAM_COLOR|STREAM_UV0); CModelRData::RenderModels(STREAM_POS|STREAM_COLOR|STREAM_UV0); - - // switch off client states - glDisableClientState(GL_TEXTURE_COORD_ARRAY); - glDisableClientState(GL_VERTEX_ARRAY); - glDisableClientState(GL_COLOR_ARRAY); + CModelRData::FinishRender(STREAM_POS|STREAM_COLOR|STREAM_UV0); } void CRenderer::RenderModels() @@ -981,14 +1051,10 @@ void CRenderer::RenderModels() glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); - // .. and some client states - glEnableClientState(GL_VERTEX_ARRAY); - - // render each model + // render all non-transparent, non-player models + CModelRData::SetupRender(STREAM_POS); CModelRData::RenderModels(STREAM_POS); - - // .. and switch off the client states - glDisableClientState(GL_VERTEX_ARRAY); + CModelRData::FinishRender(STREAM_POS); // .. and restore the renderstates glDisable(GL_BLEND); diff --git a/source/renderer/Renderer.h b/source/renderer/Renderer.h index f07004229c..db21a1dd94 100755 --- a/source/renderer/Renderer.h +++ b/source/renderer/Renderer.h @@ -34,6 +34,7 @@ class CMaterial; class CLightEnv; class CTexture; class CTerrain; +class RenderPathVertexShader; // rendering modes @@ -107,7 +108,19 @@ public: OPT_SHADOWCOLOR, OPT_LODBIAS }; - + + enum RenderPath { + // If no rendering path is configured explicitly, the renderer + // will choose the path when Open() is called. + RP_DEFAULT, + + // Classic fixed function. + RP_FIXED, + + // Use (GL 2.0) vertex shaders for T&L when possible. + RP_VERTEXSHADER + }; + // stats class - per frame counts of number of draw calls, poly counts etc struct Stats { // set all stats to zero @@ -153,7 +166,11 @@ public: void SetOptionColor(enum Option opt,const RGBAColor& value); void SetOptionFloat(enum Option opt, float val); const RGBAColor& GetOptionColor(enum Option opt) const; - + void SetRenderPath(RenderPath rp); + RenderPath GetRenderPath() const { return m_Options.m_RenderPath; } + static CStr GetRenderPathName(RenderPath rp); + static RenderPath GetRenderPathByName(CStr name); + // return view width int GetWidth() const { return m_Width; } // return view height @@ -238,14 +255,15 @@ public: // return stats accumulated for current frame const Stats& GetStats() { return m_Stats; } - // return the current light environment - const CLightEnv &GetLightEnv() { return *m_LightEnv; } + // return the current light environment + const CLightEnv &GetLightEnv() { return *m_LightEnv; } protected: friend class CVertexBuffer; friend class CPatchRData; friend class CModelRData; friend class CTransparencyRenderer; friend class CPlayerRenderer; + friend class RenderPathVertexShader; // patch rendering stuff void RenderPatchSubmissions(); @@ -266,6 +284,11 @@ protected: void CalcShadowMatrices(); void CalcShadowBounds(CBound& bounds); + // render path stuff + bool InitRenderPathVertexShader(); + void ShutdownRenderPathVertexShader(); + void InitRenderPath(); + // RENDERER DATA: // view width int m_Width; @@ -324,6 +347,7 @@ protected: bool m_Shadows; RGBAColor m_ShadowColor; float m_LodBias; + RenderPath m_RenderPath; } m_Options; // build card cap bits void EnumCaps(); @@ -331,6 +355,10 @@ protected: Stats m_Stats; // active textures on each unit GLuint m_ActiveTextures[MaxTextureUnits]; + + // Additional state that is only available when the vertex shader + // render path is used (according to m_Options.m_RenderPath) + RenderPathVertexShader* m_VertexShader; }; diff --git a/source/renderer/TransparencyRenderer.cpp b/source/renderer/TransparencyRenderer.cpp index eac9cf23f7..7849059bf3 100755 --- a/source/renderer/TransparencyRenderer.cpp +++ b/source/renderer/TransparencyRenderer.cpp @@ -49,11 +49,6 @@ void CTransparencyRenderer::Render() glPolygonMode(GL_FRONT_AND_BACK,GL_LINE); } - // switch on client states - glEnableClientState(GL_VERTEX_ARRAY); - glEnableClientState(GL_COLOR_ARRAY); - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_REPLACE); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_CONSTANT); @@ -69,7 +64,9 @@ void CTransparencyRenderer::Render() // render everything with color writes off to setup depth buffer correctly glColorMask(0,0,0,0); + CModelRData::SetupRender(STREAM_POS|STREAM_UV0); RenderObjectsStreams(STREAM_POS|STREAM_UV0); + CModelRData::FinishRender(STREAM_POS|STREAM_UV0); glColorMask(1,1,1,1); glEnable(GL_BLEND); @@ -88,7 +85,9 @@ void CTransparencyRenderer::Render() // Set the proper LOD bias glTexEnvf(GL_TEXTURE_FILTER_CONTROL, GL_TEXTURE_LOD_BIAS, g_Renderer.m_Options.m_LodBias); + CModelRData::SetupRender(STREAM_POS|STREAM_COLOR|STREAM_UV0); RenderObjectsStreams(STREAM_POS|STREAM_COLOR|STREAM_UV0); + CModelRData::FinishRender(STREAM_POS|STREAM_COLOR|STREAM_UV0); glDisable(GL_BLEND); glDisable(GL_ALPHA_TEST); @@ -116,11 +115,10 @@ void CTransparencyRenderer::Render() glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); - // .. and some client states - glEnableClientState(GL_VERTEX_ARRAY); - // render each model + CModelRData::SetupRender(STREAM_POS); RenderObjectsStreams(STREAM_POS); + CModelRData::FinishRender(STREAM_POS); // .. and switch off the client states glDisableClientState(GL_VERTEX_ARRAY); @@ -163,9 +161,6 @@ void CTransparencyRenderer::RenderShadows() { if (m_Objects.size()==0) return; - // switch on client states - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - glDepthMask(0); glEnable(GL_BLEND); @@ -182,13 +177,12 @@ void CTransparencyRenderer::RenderShadows() // Set the proper LOD bias glTexEnvf(GL_TEXTURE_FILTER_CONTROL, GL_TEXTURE_LOD_BIAS, g_Renderer.m_Options.m_LodBias); + CModelRData::SetupRender(STREAM_POS|STREAM_UV0); RenderObjectsStreams(STREAM_POS|STREAM_UV0,MODELFLAG_CASTSHADOWS); + CModelRData::FinishRender(STREAM_POS|STREAM_UV0); glDepthMask(1); glDisable(GL_BLEND); - - // switch off client states - glDisableClientState(GL_TEXTURE_COORD_ARRAY); } /////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/source/renderer/VertexArray.h b/source/renderer/VertexArray.h index bc43af3949..f89cbe7c9f 100644 --- a/source/renderer/VertexArray.h +++ b/source/renderer/VertexArray.h @@ -11,6 +11,11 @@ public: typedef T Type; public: + VertexArrayIterator() : + m_Data(0), m_Stride(0) + { + } + VertexArrayIterator(char* data, size_t stride) : m_Data(data), m_Stride(stride) {