diff --git a/binaries/data/mods/public/maps/scenarios/_default.xml b/binaries/data/mods/public/maps/scenarios/_default.xml index b5f6afb3a0..47a883a9fb 100644 --- a/binaries/data/mods/public/maps/scenarios/_default.xml +++ b/binaries/data/mods/public/maps/scenarios/_default.xml @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:11974496de6fda5d5c21bad61de1515be486120e4154b31d3bd12e4769f6fb98 -size 1371 +oid sha256:51dcf7313ce4101c47a26ed7979fa07defe737082e6aa04bcd48734598c8dc9d +size 1428 diff --git a/binaries/data/mods/public/shaders/model_common.fp b/binaries/data/mods/public/shaders/model_common.fp new file mode 100644 index 0000000000..ea6142f7d1 --- /dev/null +++ b/binaries/data/mods/public/shaders/model_common.fp @@ -0,0 +1,64 @@ +!!ARBfp1.0 +#ifdef USE_FP_SHADOW + OPTION ARB_fragment_program_shadow; +#endif + +#ifdef LIGHTING_MODEL_old + #define CLAMP_LIGHTING +#endif + +#ifdef CLAMP_LIGHTING // for compat with old scenarios that expect clamped lighting + #define MAD_MAYBE_SAT MAD_SAT +#else + #define MAD_MAYBE_SAT MAD +#endif + +#ifdef USE_OBJECTCOLOR + PARAM objectColor = program.local[0]; +#endif + +PARAM ambient = program.local[1]; + +TEMP tex; +TEMP temp; +TEMP diffuse; +TEMP color; + +TEX tex, fragment.texcoord[0], texture[0], 2D; +#ifdef USE_TRANSPARENT + MOV result.color.a, tex; +#endif + +// Apply player-coloring based on texture alpha +#ifdef USE_OBJECTCOLOR + LRP temp.rgb, objectColor, 1.0, tex.a; + MUL color.rgb, tex, temp; +#else + MOV color.rgb, tex; +#endif + +// Compute color = texture * (ambient + diffuse*shadow) +// (diffuse is 2*fragment.color due to clamp-avoidance in the vertex program) +#ifdef USE_SHADOW + #ifdef USE_FP_SHADOW + TEX temp, fragment.texcoord[1], texture[1], SHADOW2D; + #else + TEX tex, fragment.texcoord[1], texture[1], 2D; + MOV_SAT temp.z, fragment.texcoord[1].z; + SGE temp, tex.x, temp.z; + #endif + MUL diffuse.rgb, fragment.color, 2.0; + MAD_MAYBE_SAT temp.rgb, diffuse, temp, ambient; + MUL color.rgb, color, temp; +#else + MAD_MAYBE_SAT temp.rgb, fragment.color, 2.0, ambient; + MUL color.rgb, color, temp; +#endif + +// Multiply everything by the LOS texture +TEX tex.a, fragment.texcoord[2], texture[2], 2D; +MUL color.rgb, color, tex.a; + +MOV result.color.rgb, color; + +END diff --git a/binaries/data/mods/public/shaders/model_common.vp b/binaries/data/mods/public/shaders/model_common.vp new file mode 100644 index 0000000000..797f5d1286 --- /dev/null +++ b/binaries/data/mods/public/shaders/model_common.vp @@ -0,0 +1,54 @@ +!!ARBvp1.0 +PARAM sunDir = program.local[0]; +PARAM sunColor = program.local[1]; +PARAM losTransform = program.local[2]; +PARAM shadowTransform[4] = { program.local[3..6] }; +TEMP lighting; + +//// Compute position and normal: + +#ifdef USE_INSTANCING + PARAM instancingTransform[4] = { program.local[7..10] }; + TEMP position; + TEMP normal; + DP4 position.x, instancingTransform[0], vertex.position; + DP4 position.y, instancingTransform[1], vertex.position; + DP4 position.z, instancingTransform[2], vertex.position; + MOV position.w, 1.0; + DP3 normal.x, instancingTransform[0], vertex.normal; + DP3 normal.y, instancingTransform[1], vertex.normal; + DP3 normal.z, instancingTransform[2], vertex.normal; +#else + ATTRIB position = vertex.position; + ATTRIB normal = vertex.normal; +#endif + +DP4 result.position.x, state.matrix.mvp.row[0], position; +DP4 result.position.y, state.matrix.mvp.row[1], position; +DP4 result.position.z, state.matrix.mvp.row[2], position; +DP4 result.position.w, state.matrix.mvp.row[3], position; + +//// Compute lighting: + +// Diffuse factor +DP3 lighting, normal, -sunDir; +MAX lighting, 0.0, lighting; +// Scale diffuse to allow overbrightness (since result.color will be clamped to [0, 1]) +MUL lighting, lighting, 0.5; +// Apply light colour +MUL result.color, lighting, sunColor; + +//// Texture coordinates: + +MOV result.texcoord[0], vertex.texcoord[0]; + +#ifdef USE_SHADOW + DP4 result.texcoord[1].x, shadowTransform[0], position; + DP4 result.texcoord[1].y, shadowTransform[1], position; + DP4 result.texcoord[1].z, shadowTransform[2], position; + DP4 result.texcoord[1].w, shadowTransform[3], position; +#endif + +MAD result.texcoord[2], position.xzzz, losTransform.x, losTransform.y; + +END diff --git a/binaries/data/mods/public/shaders/model_common.xml b/binaries/data/mods/public/shaders/model_common.xml new file mode 100644 index 0000000000..69bee47d8e --- /dev/null +++ b/binaries/data/mods/public/shaders/model_common.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/binaries/data/mods/public/shaders/model_common_instancing.xml b/binaries/data/mods/public/shaders/model_common_instancing.xml new file mode 100644 index 0000000000..4702f02a45 --- /dev/null +++ b/binaries/data/mods/public/shaders/model_common_instancing.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/binaries/data/mods/public/shaders/program.rng b/binaries/data/mods/public/shaders/program.rng new file mode 100644 index 0000000000..06efdd0ac4 --- /dev/null +++ b/binaries/data/mods/public/shaders/program.rng @@ -0,0 +1,76 @@ + + + + + + + arb + glsl + + + + + + + + + + + + + + + + + + + + + + + pos + normal + color + uv0 + uv1 + uv2 + uv3 + + + + + + + + + + + + + + + + + + + + + + + + float + vec2 + vec3 + vec4 + mat2 + mat3 + mat4 + sampler2D + sampler2DShadow + samplerCube + + + + + + diff --git a/binaries/data/mods/public/shaders/solid.fp b/binaries/data/mods/public/shaders/solid.fp new file mode 100644 index 0000000000..4c67baec5e --- /dev/null +++ b/binaries/data/mods/public/shaders/solid.fp @@ -0,0 +1,3 @@ +!!ARBfp1.0 +MOV result.color, fragment.color; +END diff --git a/binaries/data/mods/public/shaders/solid.vp b/binaries/data/mods/public/shaders/solid.vp new file mode 100644 index 0000000000..365d5d30af --- /dev/null +++ b/binaries/data/mods/public/shaders/solid.vp @@ -0,0 +1,20 @@ +!!ARBvp1.0 +#ifdef USE_INSTANCING + PARAM instancingTransform[4] = { program.local[0..3] }; + TEMP position; + DP4 position.x, instancingTransform[0], vertex.position; + DP4 position.y, instancingTransform[1], vertex.position; + DP4 position.z, instancingTransform[2], vertex.position; + MOV position.w, 1.0; +#else + ATTRIB position = vertex.position; +#endif + +DP4 result.position.x, state.matrix.mvp.row[0], position; +DP4 result.position.y, state.matrix.mvp.row[1], position; +DP4 result.position.z, state.matrix.mvp.row[2], position; +DP4 result.position.w, state.matrix.mvp.row[3], position; + +MOV result.color, vertex.color; + +END diff --git a/binaries/data/mods/public/shaders/solid.xml b/binaries/data/mods/public/shaders/solid.xml new file mode 100644 index 0000000000..53d668e50d --- /dev/null +++ b/binaries/data/mods/public/shaders/solid.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/binaries/data/mods/public/shaders/solid_color.fp b/binaries/data/mods/public/shaders/solid_color.fp new file mode 100644 index 0000000000..14c43060d8 --- /dev/null +++ b/binaries/data/mods/public/shaders/solid_color.fp @@ -0,0 +1,3 @@ +!!ARBfp1.0 +MOV result.color, program.local[0]; +END diff --git a/binaries/data/mods/public/shaders/solid_instancing.xml b/binaries/data/mods/public/shaders/solid_instancing.xml new file mode 100644 index 0000000000..53f2f20bec --- /dev/null +++ b/binaries/data/mods/public/shaders/solid_instancing.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/binaries/data/mods/public/shaders/solid_player.xml b/binaries/data/mods/public/shaders/solid_player.xml new file mode 100644 index 0000000000..d2d772410d --- /dev/null +++ b/binaries/data/mods/public/shaders/solid_player.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/binaries/data/mods/public/shaders/solid_player_instancing.xml b/binaries/data/mods/public/shaders/solid_player_instancing.xml new file mode 100644 index 0000000000..64e50dd073 --- /dev/null +++ b/binaries/data/mods/public/shaders/solid_player_instancing.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/binaries/data/mods/public/shaders/solid_tex.fp b/binaries/data/mods/public/shaders/solid_tex.fp new file mode 100644 index 0000000000..cc315b0280 --- /dev/null +++ b/binaries/data/mods/public/shaders/solid_tex.fp @@ -0,0 +1,3 @@ +!!ARBfp1.0 +TEX result.color, fragment.texcoord[0], texture[0], 2D; +END diff --git a/binaries/data/mods/public/shaders/solid_tex.vp b/binaries/data/mods/public/shaders/solid_tex.vp new file mode 100644 index 0000000000..d252869620 --- /dev/null +++ b/binaries/data/mods/public/shaders/solid_tex.vp @@ -0,0 +1,18 @@ +!!ARBvp1.0 +#ifdef USE_INSTANCING + PARAM instancingTransform[4] = { program.local[0..3] }; + TEMP position; + DP4 position.x, instancingTransform[0], vertex.position; + DP4 position.y, instancingTransform[1], vertex.position; + DP4 position.z, instancingTransform[2], vertex.position; + MOV position.w, 1.0; +#else + ATTRIB position = vertex.position; +#endif + +DP4 result.position.x, state.matrix.mvp.row[0], position; +DP4 result.position.y, state.matrix.mvp.row[1], position; +DP4 result.position.z, state.matrix.mvp.row[2], position; +DP4 result.position.w, state.matrix.mvp.row[3], position; +MOV result.texcoord[0], vertex.texcoord[0]; +END diff --git a/binaries/data/mods/public/shaders/solid_tex.xml b/binaries/data/mods/public/shaders/solid_tex.xml new file mode 100644 index 0000000000..c62b3be0d5 --- /dev/null +++ b/binaries/data/mods/public/shaders/solid_tex.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/binaries/data/mods/public/shaders/terrain_base.xml b/binaries/data/mods/public/shaders/terrain_base.xml new file mode 100644 index 0000000000..6d4caf1453 --- /dev/null +++ b/binaries/data/mods/public/shaders/terrain_base.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/binaries/data/mods/public/shaders/terrain_blend.xml b/binaries/data/mods/public/shaders/terrain_blend.xml new file mode 100644 index 0000000000..f8e7d06b90 --- /dev/null +++ b/binaries/data/mods/public/shaders/terrain_blend.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/binaries/data/mods/public/shaders/terrain_common.fp b/binaries/data/mods/public/shaders/terrain_common.fp new file mode 100644 index 0000000000..0b1232493e --- /dev/null +++ b/binaries/data/mods/public/shaders/terrain_common.fp @@ -0,0 +1,62 @@ +!!ARBfp1.0 +#ifdef USE_FP_SHADOW + OPTION ARB_fragment_program_shadow; +#endif + +#ifdef LIGHTING_MODEL_old + #define CLAMP_LIGHTING +#endif + +#ifdef CLAMP_LIGHTING // for compat with old scenarios that expect clamped lighting + #define MAD_MAYBE_SAT MAD_SAT +#else + #define MAD_MAYBE_SAT MAD +#endif + +PARAM ambient = program.local[0]; + +TEMP tex; +TEMP temp; +TEMP diffuse; +TEMP color; + +#ifdef BLEND + // Use alpha from blend texture + // TODO: maybe we should invert the texture instead of doing SUB here? + TEX tex.a, fragment.texcoord[1], texture[1], 2D; + SUB result.color.a, 1.0, tex.a; +#endif + +// Load diffuse colour +TEX color, fragment.texcoord[0], texture[0], 2D; + +#ifdef DECAL + // Use alpha from main texture + MOV result.color.a, color; +#endif + +// Compute color = texture * (ambient + diffuse*shadow) +// (diffuse is 2*fragment.color due to clamp-avoidance in the vertex program) +#ifdef USE_SHADOW + #ifdef USE_FP_SHADOW + TEX temp, fragment.texcoord[2], texture[2], SHADOW2D; + #else + TEX tex, fragment.texcoord[2], texture[2], 2D; + MOV_SAT temp.z, fragment.texcoord[2].z; + SGE temp, tex.x, temp.z; + #endif + MUL diffuse.rgb, fragment.color, 2.0; + MAD_MAYBE_SAT temp.rgb, diffuse, temp, ambient; + MUL color.rgb, color, temp; +#else + MAD_MAYBE_SAT temp.rgb, fragment.color, 2.0, ambient; + MUL color.rgb, color, temp; +#endif + +// Multiply everything by the LOS texture +TEX tex.a, fragment.texcoord[3], texture[3], 2D; +MUL color.rgb, color, tex.a; + +MOV result.color.rgb, color; + +END diff --git a/binaries/data/mods/public/shaders/terrain_common.vp b/binaries/data/mods/public/shaders/terrain_common.vp new file mode 100644 index 0000000000..5117637af9 --- /dev/null +++ b/binaries/data/mods/public/shaders/terrain_common.vp @@ -0,0 +1,42 @@ +!!ARBvp1.0 +PARAM sunColor = program.local[0]; +PARAM losTransform = program.local[1]; +PARAM shadowTransform[4] = { program.local[2..5] }; + +TEMP lighting; + +//// Compute position and normal: + +ATTRIB position = vertex.position; + +DP4 result.position.x, state.matrix.mvp.row[0], position; +DP4 result.position.y, state.matrix.mvp.row[1], position; +DP4 result.position.z, state.matrix.mvp.row[2], position; +DP4 result.position.w, state.matrix.mvp.row[3], position; + +//// Compute lighting: + +// Diffuse factor is precomputed in vertex attribute +// Scale diffuse to allow overbrightness (since result.color will be clamped to [0, 1]) +MUL lighting, vertex.color, 0.5; +// Apply light colour +MUL result.color, lighting, sunColor; + +//// Texture coordinates: + +MOV result.texcoord[0], vertex.texcoord[0]; + +#ifdef BLEND + MOV result.texcoord[1], vertex.texcoord[1]; +#endif + +#ifdef USE_SHADOW + DP4 result.texcoord[2].x, shadowTransform[0], position; + DP4 result.texcoord[2].y, shadowTransform[1], position; + DP4 result.texcoord[2].z, shadowTransform[2], position; + DP4 result.texcoord[2].w, shadowTransform[3], position; +#endif + +MAD result.texcoord[3], position.xzzz, losTransform.x, losTransform.y; + +END diff --git a/binaries/data/mods/public/shaders/terrain_decal.xml b/binaries/data/mods/public/shaders/terrain_decal.xml new file mode 100644 index 0000000000..ed234d5d18 --- /dev/null +++ b/binaries/data/mods/public/shaders/terrain_decal.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/source/graphics/GameView.cpp b/source/graphics/GameView.cpp index f1f2f6b596..4a9ff19fe4 100644 --- a/source/graphics/GameView.cpp +++ b/source/graphics/GameView.cpp @@ -442,7 +442,7 @@ void CGameView::RegisterInit() } -void CGameView::Render() +void CGameView::BeginFrame() { if (m->LockCullCamera == false) { @@ -462,6 +462,11 @@ void CGameView::Render() CheckLightEnv(); + m->Game->CachePlayerColours(); +} + +void CGameView::Render() +{ g_Renderer.RenderScene(this); } @@ -548,6 +553,9 @@ void CGameView::CheckLightEnv() if (m->CachedLightEnv == g_LightEnv) return; + if (m->CachedLightEnv.GetLightingModel() != g_LightEnv.GetLightingModel()) + g_Renderer.MakeShadersDirty(); + m->CachedLightEnv = g_LightEnv; CTerrain* pTerrain = m->Game->GetWorld()->GetTerrain(); diff --git a/source/graphics/GameView.h b/source/graphics/GameView.h index ba87e1cfbd..b70af3b8a6 100644 --- a/source/graphics/GameView.h +++ b/source/graphics/GameView.h @@ -78,7 +78,7 @@ public: // *presentation* void Update(float DeltaTime); - // Render: Render the World + void BeginFrame(); void Render(); InReaction HandleEvent(const SDL_Event_* ev); diff --git a/source/graphics/LOSTexture.cpp b/source/graphics/LOSTexture.cpp index 8e44a00bdd..6d8a2ca54c 100644 --- a/source/graphics/LOSTexture.cpp +++ b/source/graphics/LOSTexture.cpp @@ -77,6 +77,17 @@ void CLOSTexture::BindTexture(int unit) g_Renderer.BindTexture(unit, m_Texture); } +GLuint CLOSTexture::GetTexture() +{ + if (m_Dirty) + { + RecomputeTexture(0); + m_Dirty = false; + } + + return m_Texture; +} + const float* CLOSTexture::GetTextureMatrix() { debug_assert(!m_Dirty); diff --git a/source/graphics/LOSTexture.h b/source/graphics/LOSTexture.h index 833f86e6a8..6a6f147dba 100644 --- a/source/graphics/LOSTexture.h +++ b/source/graphics/LOSTexture.h @@ -47,6 +47,13 @@ public: */ void BindTexture(int unit); + /** + * Recomputes the LOS texture if necessary, and returns the texture handle. + * Also potentially switches the current active texture unit, and enables texturing on it. + * The texture is in 8-bit ALPHA format. + */ + GLuint GetTexture(); + /** * Returns a matrix to map (x,y,z) world coordinates onto (u,v) LOS texture * coordinates, in the form expected by glLoadMatrixf. diff --git a/source/graphics/LightEnv.cpp b/source/graphics/LightEnv.cpp index 00e9a71cc1..1ce3f7049f 100644 --- a/source/graphics/LightEnv.cpp +++ b/source/graphics/LightEnv.cpp @@ -30,6 +30,7 @@ CLightEnv::CLightEnv() : m_Elevation(DEGTORAD(45)), m_Rotation(DEGTORAD(315)), m_TerrainShadowTransparency(0.0), + m_LightingModel("standard"), m_SunColor(1.5, 1.5, 1.5), m_TerrainAmbientColor(0x50/255.f, 0x60/255.f, 0x85/255.f), m_UnitsAmbientColor(0x80/255.f, 0x80/255.f, 0x80/255.f) diff --git a/source/graphics/LightEnv.h b/source/graphics/LightEnv.h index dd23ed3709..4f8e304745 100644 --- a/source/graphics/LightEnv.h +++ b/source/graphics/LightEnv.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2009 Wildfire Games. +/* Copyright (C) 2011 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -22,7 +22,8 @@ #ifndef INCLUDED_LIGHTENV #define INCLUDED_LIGHTENV -#include "Color.h" +#include "graphics/Color.h" +#include "maths/MathUtil.h" #include "maths/Vector3D.h" class CMapWriter; @@ -56,11 +57,18 @@ private: * m_TerrainShadowTransparency: Fraction of diffuse light that reaches shadowed terrain. * A value of 0.0 means shadowed polygons get only ambient light, while a value of 1.0 * means shadows don't have any effect at all. + * TODO: probably delete this, since it's never used and always set to 0.0. */ float m_TerrainShadowTransparency; CVector3D m_SunDir; + /** + * A string that shaders use to determine what lighting model to implement. + * Current recognised values are "old" and "standard". + */ + std::string m_LightingModel; + public: RGBColor m_SunColor; RGBColor m_TerrainAmbientColor; @@ -73,12 +81,15 @@ public: float GetRotation() const { return m_Rotation; } const CVector3D& GetSunDir() const { return m_SunDir; } float GetTerrainShadowTransparency() const { return m_TerrainShadowTransparency; } + const std::string& GetLightingModel() const { return m_LightingModel; } void SetElevation(float f); void SetRotation(float f); void SetTerrainShadowTransparency(float f); + void SetLightingModel(const std::string& model) { m_LightingModel = model; } + /** * EvaluateTerrain: Calculate brightness of a point of the terrain with the given normal * vector. @@ -130,12 +141,37 @@ public: color = CVector3D(0,0,0); } + /** + * Compute the diffuse sun lighting. + * If @p includeSunColor is set, the return value includes the sun color. + * (If sun overbrightness is enabled, this might result in clamping). + * Otherwise it returns a factor that the sun color should be multiplied by. + */ + SColor4ub EvaluateDiffuse(const CVector3D& normal, bool includeSunColor) const + { + float dot = -normal.Dot(m_SunDir); + + if (dot <= 0) + return SColor4ub(0, 0, 0, 255); + + if (includeSunColor) + { + return ConvertRGBColorTo4ub(m_SunColor * dot); + } + else + { + int c = clamp((int)(dot * 255), 0, 255); + return SColor4ub(c, c, c, 255); + } + } + // Comparison operators bool operator==(const CLightEnv& o) const { return m_Elevation == o.m_Elevation && m_Rotation == o.m_Rotation && m_TerrainShadowTransparency == o.m_TerrainShadowTransparency && + m_LightingModel == o.m_LightingModel && m_SunColor == o.m_SunColor && m_TerrainAmbientColor == o.m_TerrainAmbientColor && m_UnitsAmbientColor == o.m_UnitsAmbientColor; diff --git a/source/graphics/MapReader.cpp b/source/graphics/MapReader.cpp index 40119fe39c..a8b7678f26 100644 --- a/source/graphics/MapReader.cpp +++ b/source/graphics/MapReader.cpp @@ -51,6 +51,9 @@ CMapReader::CMapReader() : xml_reader(0), m_PatchesPerSide(0) { cur_terrain_tex = 0; // important - resets generator state + + // Maps that don't override the default probably want the old lighting model + m_LightEnv.SetLightingModel("old"); } // LoadMap: try to load the map from given file; reinitialise the scene to new data if successful @@ -538,6 +541,7 @@ void CXMLReader::ReadEnvironment(XMBElement parent) { #define EL(x) int el_##x = xmb_file.GetElementID(#x) #define AT(x) int at_##x = xmb_file.GetAttributeID(#x) + EL(lightingmodel); EL(skyset); EL(suncolour); EL(sunelevation); @@ -566,7 +570,11 @@ void CXMLReader::ReadEnvironment(XMBElement parent) XMBAttributeList attrs = element.GetAttributes(); - if (element_name == el_skyset) + if (element_name == el_lightingmodel) + { + m_MapReader.m_LightEnv.SetLightingModel(element.GetText()); + } + else if (element_name == el_skyset) { if (m_MapReader.pSkyMan) m_MapReader.pSkyMan->SetSkySet(element.GetText().FromUTF8()); @@ -1213,6 +1221,8 @@ int CMapReader::ParseEnvironment() return 0; } + m_LightEnv.SetLightingModel("standard"); + std::wstring skySet; GET_ENVIRONMENT_PROPERTY(envObj.get(), SkySet, skySet) pSkyMan->SetSkySet(skySet); diff --git a/source/graphics/MapWriter.cpp b/source/graphics/MapWriter.cpp index 8f70ed69d1..3303a85964 100644 --- a/source/graphics/MapWriter.cpp +++ b/source/graphics/MapWriter.cpp @@ -189,6 +189,7 @@ void CMapWriter::WriteXML(const VfsPath& filename, { XML_Element("Environment"); + XML_Setting("LightingModel", pLightEnv->GetLightingModel()); XML_Setting("SkySet", pSkyMan->GetSkySet()); { XML_Element("SunColour"); diff --git a/source/graphics/Material.cpp b/source/graphics/Material.cpp index e30b3d55c2..271163605f 100644 --- a/source/graphics/Material.cpp +++ b/source/graphics/Material.cpp @@ -87,9 +87,6 @@ SMaterialColor CMaterial::GetObjectColor() if (m_UseTextureColor) return m_TextureColor; - debug_assert(m_UsePlayerColor); - // this should never be called unless IsPlayer returned true - return GetPlayerColor(); } diff --git a/source/graphics/Material.h b/source/graphics/Material.h index 986debb0b0..adf5926a90 100644 --- a/source/graphics/Material.h +++ b/source/graphics/Material.h @@ -19,29 +19,12 @@ #define INCLUDED_MATERIAL #include "ps/CStr.h" +#include "ps/Overlay.h" #include "simulation2/helpers/Player.h" // FIXME: This material system is almost entirely unused and probably broken -struct CColor; - -struct SMaterialColor -{ -public: - float r; - float g; - float b; - float a; - - SMaterialColor() : r(0.0f), g(0.0f), b(0.0f), a(1.0f) {} - SMaterialColor(float _r, float _g, float _b, float _a) - { - r = _r; - g = _g; - b = _b; - a = _a; - } -}; +typedef CColor SMaterialColor; class CMaterial { diff --git a/source/graphics/ShaderManager.cpp b/source/graphics/ShaderManager.cpp new file mode 100644 index 0000000000..fb4c46d640 --- /dev/null +++ b/source/graphics/ShaderManager.cpp @@ -0,0 +1,210 @@ +/* Copyright (C) 2011 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#include "precompiled.h" + +#include "ShaderManager.h" + +#include "lib/timer.h" +#include "lib/utf8.h" +#include "ps/CLogger.h" +#include "ps/Filesystem.h" +#include "ps/XML/Xeromyces.h" +#include "ps/XML/XMLWriter.h" +#include "renderer/Renderer.h" + +TIMER_ADD_CLIENT(tc_ShaderValidation); + +CShaderManager::CShaderManager() +{ +#if USE_SHADER_XML_VALIDATION + { + TIMER_ACCRUE(tc_ShaderValidation); + CVFSFile grammar; + if (grammar.Load(g_VFS, L"shaders/program.rng") != PSRETURN_OK) + LOGERROR(L"Failed to read grammar shaders/program.rng"); + else + { + if (!m_Validator.LoadGrammar(grammar.GetAsString())) + LOGERROR(L"Failed to load grammar shaders/program.rng"); + } + } +#endif + + // Allow hotloading of textures + RegisterFileReloadFunc(ReloadChangedFileCB, this); +} + +CShaderManager::~CShaderManager() +{ + UnregisterFileReloadFunc(ReloadChangedFileCB, this); +} + +CShaderProgramPtr CShaderManager::LoadProgram(const char* name, const std::map& defines) +{ + CacheKey key = { name, defines }; + std::map::iterator it = m_Cache.find(key); + if (it != m_Cache.end()) + return it->second; + + CShaderProgramPtr program; + if (NewProgram(name, defines, program) != PSRETURN_OK) + { + LOGERROR(L"Failed to load shader '%hs'", name); + program = CShaderProgramPtr(); + } + + m_Cache[key] = program; + return program; +} + +bool CShaderManager::NewProgram(const char* name, const std::map& baseDefines, CShaderProgramPtr& program) +{ + VfsPath xmlFilename = L"shaders/" + wstring_from_utf8(name) + L".xml"; + + CXeromyces XeroFile; + PSRETURN ret = XeroFile.Load(g_VFS, xmlFilename); + if (ret != PSRETURN_OK) + return false; + +#if USE_SHADER_XML_VALIDATION + { + TIMER_ACCRUE(tc_ShaderValidation); + + // Serialize the XMB data and pass it to the validator + XML_Start(); + XML_SetPrettyPrint(false); + XML_WriteXMB(XeroFile); + bool ok = m_Validator.ValidateEncoded(wstring_from_utf8(name), XML_GetOutput()); + if (!ok) + return false; + } +#endif + + // Define all the elements and attributes used in the XML file +#define EL(x) int el_##x = XeroFile.GetElementID(#x) +#define AT(x) int at_##x = XeroFile.GetAttributeID(#x) + EL(vertex); + EL(fragment); + EL(define); + EL(uniform); + EL(attrib); + EL(stream); + AT(type); + AT(file); + AT(name); + AT(value); + AT(loc); +#undef AT +#undef EL + + XMBElement Root = XeroFile.GetRoot(); + + bool isGLSL = (Root.GetAttributes().GetNamedItem(at_type) == "glsl"); + VfsPath vertexFile; + VfsPath fragmentFile; + std::map defines = baseDefines; + std::map vertexUniforms; + std::map fragmentUniforms; + int streamFlags = 0; + + XERO_ITER_EL(Root, Child) + { + if (Child.GetNodeName() == el_define) + { + defines[Child.GetAttributes().GetNamedItem(at_name)] = Child.GetAttributes().GetNamedItem(at_value); + } + else if (Child.GetNodeName() == el_vertex) + { + vertexFile = L"shaders/" + Child.GetAttributes().GetNamedItem(at_file).FromUTF8(); + + XERO_ITER_EL(Child, Param) + { + if (Param.GetNodeName() == el_uniform) + { + vertexUniforms[Param.GetAttributes().GetNamedItem(at_name)] = Param.GetAttributes().GetNamedItem(at_loc).ToInt(); + } + else if (Param.GetNodeName() == el_stream) + { + CStr StreamName = Param.GetAttributes().GetNamedItem(at_name); + if (StreamName == "pos") + streamFlags |= STREAM_POS; + else if (StreamName == "normal") + streamFlags |= STREAM_NORMAL; + else if (StreamName == "color") + streamFlags |= STREAM_COLOR; + else if (StreamName == "uv0") + streamFlags |= STREAM_UV0; + else if (StreamName == "uv1") + streamFlags |= STREAM_UV1; + else if (StreamName == "uv2") + streamFlags |= STREAM_UV2; + else if (StreamName == "uv3") + streamFlags |= STREAM_UV3; + } + else if (Param.GetNodeName() == el_attrib) + { + // TODO: add support for vertex attributes + } + } + } + else if (Child.GetNodeName() == el_fragment) + { + fragmentFile = L"shaders/" + Child.GetAttributes().GetNamedItem(at_file).FromUTF8(); + + XERO_ITER_EL(Child, Param) + { + if (Param.GetNodeName() == el_uniform) + fragmentUniforms[Param.GetAttributes().GetNamedItem(at_name)] = Param.GetAttributes().GetNamedItem(at_loc).ToInt(); + } + } + } + + // TODO: add GLSL support + debug_assert(!isGLSL); + + program = CShaderProgramPtr(CShaderProgram::ConstructARB(vertexFile, fragmentFile, defines, vertexUniforms, fragmentUniforms, streamFlags)); + program->Reload(); + +// m_HotloadFiles[xmlFilename].insert(program); // TODO: should reload somehow when the XML changes + m_HotloadFiles[vertexFile].insert(program); + m_HotloadFiles[fragmentFile].insert(program); + + return PSRETURN_OK; +} + +/*static*/ LibError CShaderManager::ReloadChangedFileCB(void* param, const VfsPath& path) +{ + return static_cast(param)->ReloadChangedFile(path); +} + +LibError CShaderManager::ReloadChangedFile(const VfsPath& path) +{ + // Find all shaders using this file + HotloadFilesMap::iterator files = m_HotloadFiles.find(path); + if (files != m_HotloadFiles.end()) + { + // Reload all shaders using this file + for (std::set >::iterator it = files->second.begin(); it != files->second.end(); ++it) + { + if (shared_ptr program = it->lock()) + program->Reload(); + } + } + + return INFO::OK; +} diff --git a/source/graphics/ShaderManager.h b/source/graphics/ShaderManager.h new file mode 100644 index 0000000000..70e7cf3a01 --- /dev/null +++ b/source/graphics/ShaderManager.h @@ -0,0 +1,79 @@ +/* Copyright (C) 2011 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#ifndef INCLUDED_SHADERMANAGER +#define INCLUDED_SHADERMANAGER + +#define USE_SHADER_XML_VALIDATION 1 + +#include +#include + +#include "graphics/ShaderProgram.h" + +#if USE_SHADER_XML_VALIDATION +# include "ps/XML/RelaxNG.h" +#endif + +/** + * Shader manager: loads and caches shader programs. + */ +class CShaderManager +{ +public: + CShaderManager(); + ~CShaderManager(); + + /** + * Load a shader program. + * @param name name of shader XML specification (file is loaded from shaders/${name}.xml) + * @param defines key/value set of preprocessor definitions + * @return loaded program, or null pointer on error + */ + CShaderProgramPtr LoadProgram(const char* name, const std::map& defines); + +private: + bool NewProgram(const char* name, const std::map& defines, CShaderProgramPtr& program); + + static LibError ReloadChangedFileCB(void* param, const VfsPath& path); + LibError ReloadChangedFile(const VfsPath& path); + + struct CacheKey + { + std::string name; + std::map defines; + + bool operator<(const CacheKey& k) const + { + if (name < k.name) return true; + if (k.name < name) return false; + return defines < k.defines; + } + }; + + std::map m_Cache; + + // Store the set of shaders that need to be reloaded when the given file is modified + typedef boost::unordered_map > > HotloadFilesMap; + HotloadFilesMap m_HotloadFiles; + +#if USE_SHADER_XML_VALIDATION + RelaxNGValidator m_Validator; +#endif +}; + +#endif // INCLUDED_SHADERMANAGER diff --git a/source/graphics/ShaderProgram.cpp b/source/graphics/ShaderProgram.cpp new file mode 100644 index 0000000000..2ba69d8712 --- /dev/null +++ b/source/graphics/ShaderProgram.cpp @@ -0,0 +1,313 @@ +/* Copyright (C) 2011 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#include "precompiled.h" + +#include "ShaderProgram.h" + +#include "lib/res/graphics/ogl_tex.h" +#include "maths/Matrix3D.h" +#include "maths/Vector3D.h" +#include "ps/CLogger.h" +#include "ps/Filesystem.h" +#include "ps/Overlay.h" +#include "ps/Preprocessor.h" + +class CShaderProgramARB : public CShaderProgram +{ +public: + CShaderProgramARB(const VfsPath& vertexFile, const VfsPath& fragmentFile, + const std::map& defines, + const std::map& vertexIndexes, const std::map& fragmentIndexes, + int streamflags) : + CShaderProgram(streamflags), + m_VertexFile(vertexFile), m_FragmentFile(fragmentFile), + m_Defines(defines), + m_VertexIndexes(vertexIndexes), m_FragmentIndexes(fragmentIndexes) + { + pglGenProgramsARB(1, &m_VertexProgram); + pglGenProgramsARB(1, &m_FragmentProgram); + } + + ~CShaderProgramARB() + { + Unload(); + + pglDeleteProgramsARB(1, &m_VertexProgram); + pglDeleteProgramsARB(1, &m_FragmentProgram); + } + + CStr Preprocess(CPreprocessor& preprocessor, const CStr& input) + { + size_t len = 0; + char* output = preprocessor.Parse(input.c_str(), input.size(), len); + + if (!output) + { + LOGERROR(L"Shader preprocessing failed"); + return ""; + } + + CStr ret(output, len); + + // Free output if it's not inside the source string + if (!(output >= input.c_str() && output < input.c_str() + input.size())) + free(output); + + return ret; + } + + bool Compile(GLuint target, const char* targetName, GLuint program, const VfsPath& file, const CStr& code) + { + ogl_WarnIfError(); + + pglBindProgramARB(target, program); + + ogl_WarnIfError(); + + pglProgramStringARB(target, GL_PROGRAM_FORMAT_ASCII_ARB, (GLsizei)code.length(), code.c_str()); + + if (ogl_SquelchError(GL_INVALID_OPERATION)) + { + GLint errPos = 0; + glGetIntegerv(GL_PROGRAM_ERROR_POSITION_ARB, &errPos); + int errLine = std::count(code.begin(), code.begin() + errPos + 1, '\n') + 1; + char* errStr = (char*)glGetString(GL_PROGRAM_ERROR_STRING_ARB); + LOGERROR(L"Failed to compile %hs program '%ls' (line %d):\n%hs", targetName, file.string().c_str(), errLine, errStr); + return false; + } + + pglBindProgramARB(target, 0); + + ogl_WarnIfError(); + + return true; + } + + virtual void Reload() + { + Unload(); + + CVFSFile vertexFile; + if (vertexFile.Load(g_VFS, m_VertexFile) != PSRETURN_OK) + return; + + CVFSFile fragmentFile; + if (fragmentFile.Load(g_VFS, m_FragmentFile) != PSRETURN_OK) + return; + + CPreprocessor preprocessor; + for (std::map::iterator it = m_Defines.begin(); it != m_Defines.end(); ++it) + preprocessor.Define(it->first.c_str(), it->second.c_str()); + + CStr vertexCode = Preprocess(preprocessor, vertexFile.GetAsString()); + CStr fragmentCode = Preprocess(preprocessor, fragmentFile.GetAsString()); + +// printf(">>>\n%s<<<\n", vertexCode.c_str()); +// printf(">>>\n%s<<<\n", fragmentCode.c_str()); + + if (!Compile(GL_VERTEX_PROGRAM_ARB, "vertex", m_VertexProgram, m_VertexFile, vertexCode)) + return; + + if (!Compile(GL_FRAGMENT_PROGRAM_ARB, "fragment", m_FragmentProgram, m_FragmentFile, fragmentCode)) + return; + + m_IsValid = true; + } + + void Unload() + { + m_IsValid = false; + } + + virtual void Bind() + { + glEnable(GL_VERTEX_PROGRAM_ARB); + glEnable(GL_FRAGMENT_PROGRAM_ARB); + pglBindProgramARB(GL_VERTEX_PROGRAM_ARB, m_VertexProgram); + pglBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, m_FragmentProgram); + } + + virtual void Unbind() + { + glDisable(GL_VERTEX_PROGRAM_ARB); + glDisable(GL_FRAGMENT_PROGRAM_ARB); + pglBindProgramARB(GL_VERTEX_PROGRAM_ARB, 0); + pglBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, 0); + + // TODO: should unbind textures, probably + } + + int GetUniformVertexIndex(uniform_id_t id) + { + std::map::iterator it = m_VertexIndexes.find(id); + if (it == m_VertexIndexes.end()) + return -1; + return it->second; + } + + int GetUniformFragmentIndex(uniform_id_t id) + { + std::map::iterator it = m_FragmentIndexes.find(id); + if (it == m_FragmentIndexes.end()) + return -1; + return it->second; + } + + virtual bool HasTexture(texture_id_t id) + { + if (GetUniformFragmentIndex(id) != -1) + return true; + return false; + } + + virtual void BindTexture(texture_id_t id, Handle tex) + { + int index = GetUniformFragmentIndex(id); + if (index != -1) + ogl_tex_bind(tex, index); + } + + virtual void BindTexture(texture_id_t id, GLuint tex) + { + int index = GetUniformFragmentIndex(id); + if (index != -1) + { + pglActiveTextureARB((int)(GL_TEXTURE0+index)); + glBindTexture(GL_TEXTURE_2D, tex); + } + } + + virtual Binding GetUniformBinding(uniform_id_t id) + { + return Binding(GetUniformVertexIndex(id), GetUniformFragmentIndex(id)); + } + + virtual void Uniform(uniform_id_t id, int v) + { + Uniform(Binding(GetUniformVertexIndex(id), GetUniformFragmentIndex(id)), (float)v, (float)v, (float)v, (float)v); + } + + virtual void Uniform(Binding id, int v) + { + Uniform(id, (float)v, (float)v, (float)v, (float)v); + } + + virtual void Uniform(uniform_id_t id, float v) + { + Uniform(Binding(GetUniformVertexIndex(id), GetUniformFragmentIndex(id)), v, v, v, v); + } + + virtual void Uniform(Binding id, float v) + { + Uniform(id, v, v, v, v); + } + + virtual void Uniform(uniform_id_t id, float v0, float v1, float v2, float v3) + { + Uniform(Binding(GetUniformVertexIndex(id), GetUniformFragmentIndex(id)), v0, v1, v2, v3); + } + + virtual void Uniform(Binding id, float v0, float v1, float v2, float v3) + { + if (id.vertex != -1) + pglProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, (GLuint)id.vertex, v0, v1, v2, v3); + + if (id.fragment != -1) + pglProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, (GLuint)id.fragment, v0, v1, v2, v3); + } + + virtual void Uniform(uniform_id_t id, const CVector3D& v) + { + Uniform(Binding(GetUniformVertexIndex(id), GetUniformFragmentIndex(id)), v.X, v.Y, v.Z, 0.0f); + } + + virtual void Uniform(Binding id, const CVector3D& v) + { + Uniform(id, v.X, v.Y, v.Z, 0.0f); + } + + virtual void Uniform(uniform_id_t id, const CColor& v) + { + Uniform(Binding(GetUniformVertexIndex(id), GetUniformFragmentIndex(id)), v.r, v.g, v.b, v.a); + } + + virtual void Uniform(Binding id, const CColor& v) + { + Uniform(id, v.r, v.g, v.b, v.a); + } + + virtual void Uniform(uniform_id_t id, const CMatrix3D& v) + { + Uniform(Binding(GetUniformVertexIndex(id), GetUniformFragmentIndex(id)), v); + } + + virtual void Uniform(Binding id, const CMatrix3D& v) + { + if (id.vertex != -1) + { + pglProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, (GLuint)id.vertex+0, v._11, v._12, v._13, v._14); + pglProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, (GLuint)id.vertex+1, v._21, v._22, v._23, v._24); + pglProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, (GLuint)id.vertex+2, v._31, v._32, v._33, v._34); + pglProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, (GLuint)id.vertex+3, v._41, v._42, v._43, v._44); + } + + if (id.fragment != -1) + { + pglProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, (GLuint)id.fragment+0, v._11, v._12, v._13, v._14); + pglProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, (GLuint)id.fragment+1, v._21, v._22, v._23, v._24); + pglProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, (GLuint)id.fragment+2, v._31, v._32, v._33, v._34); + pglProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, (GLuint)id.fragment+3, v._41, v._42, v._43, v._44); + } + } + +private: + VfsPath m_VertexFile; + VfsPath m_FragmentFile; + std::map m_Defines; + + GLuint m_VertexProgram; + GLuint m_FragmentProgram; + + std::map m_VertexIndexes; + std::map m_FragmentIndexes; +}; + + + +CShaderProgram::CShaderProgram(int streamflags) + : m_IsValid(false), m_StreamFlags(streamflags) +{ +} + +/*static*/ CShaderProgram* CShaderProgram::ConstructARB(const VfsPath& vertexFile, const VfsPath& fragmentFile, + const std::map& defines, + const std::map& vertexIndexes, const std::map& fragmentIndexes, + int streamflags) +{ + return new CShaderProgramARB(vertexFile, fragmentFile, defines, vertexIndexes, fragmentIndexes, streamflags); +} + +bool CShaderProgram::IsValid() const +{ + return m_IsValid; +} + +int CShaderProgram::GetStreamFlags() const +{ + return m_StreamFlags; +} diff --git a/source/graphics/ShaderProgram.h b/source/graphics/ShaderProgram.h new file mode 100644 index 0000000000..5857bf9fef --- /dev/null +++ b/source/graphics/ShaderProgram.h @@ -0,0 +1,140 @@ +/* Copyright (C) 2011 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#ifndef INCLUDED_SHADERPROGRAM +#define INCLUDED_SHADERPROGRAM + +#include "lib/ogl.h" +#include "lib/file/vfs/vfs_path.h" +#include "lib/res/handle.h" +#include "ps/CStr.h" + +class CColor; +class CMatrix3D; +class CVector3D; + +/** + * A compiled vertex+fragment shader program. + * The implementation may use GL_ARB_{vertex,fragment}_program (assembly syntax) + * or GL_ARB_{vertex,fragment}_shader (GLSL); the difference is hidden from the caller. + * + * Texture/uniform IDs are typically strings, corresponding to the names defined + * in the shader .xml file. Alternatively (and more efficiently, if used extremely + * frequently), call GetUniformBinding and pass its return value as the ID. + * Setting uniforms that the shader .xml doesn't support is harmless. + */ +class CShaderProgram +{ + NONCOPYABLE(CShaderProgram); + +public: + /** + * Construct based on ARB vertex/fragment program files. + */ + static CShaderProgram* ConstructARB(const VfsPath& vertexFile, const VfsPath& fragmentFile, + const std::map& defines, + const std::map& vertexIndexes, const std::map& fragmentIndexes, + int streamflags); + + typedef const char* attrib_id_t; + typedef const char* texture_id_t; + typedef const char* uniform_id_t; + + /** + * Represents a uniform attribute binding. + */ + struct Binding + { + friend class CShaderProgramARB; + private: + Binding(int v, int f) : vertex(v), fragment(f) { } + i16 vertex; + i16 fragment; + public: + Binding() : vertex(-1), fragment(-1) { } + + /** + * Returns whether this uniform attribute is active in the shader. + * If not then there's no point calling Uniform() to set its value. + */ + bool Active() { return vertex != -1 || fragment != -1; } + }; + + virtual ~CShaderProgram() { } + + virtual void Reload() = 0; + + /** + * Returns whether this shader was successfully loaded. + */ + bool IsValid() const; + + /** + * Binds the shader into the GL context. Call this before calling Uniform() + * or trying to render with it. + */ + virtual void Bind() = 0; + + /** + * Unbinds the shader from the GL context. Call this after rendering with it. + */ + virtual void Unbind() = 0; + + /** + * Returns bitset of STREAM_* value, indicating what vertex data streams the + * vertex shader needs. + */ + int GetStreamFlags() const; + + // TODO: implement vertex attributes + GLuint GetAttribIndex(attrib_id_t id); + + /** + * Returns whether the shader needs the texture with the given name. + */ + virtual bool HasTexture(texture_id_t id) = 0; + + virtual void BindTexture(texture_id_t id, Handle tex) = 0; + + virtual void BindTexture(texture_id_t id, GLuint tex) = 0; + + virtual Binding GetUniformBinding(uniform_id_t id) = 0; + + virtual void Uniform(uniform_id_t id, int v) = 0; + virtual void Uniform(uniform_id_t id, float v) = 0; + virtual void Uniform(uniform_id_t id, float v0, float v1, float v2, float v3) = 0; + virtual void Uniform(uniform_id_t id, const CVector3D& v) = 0; + virtual void Uniform(uniform_id_t id, const CColor& v) = 0; + virtual void Uniform(uniform_id_t id, const CMatrix3D& v) = 0; + + virtual void Uniform(Binding id, int v) = 0; + virtual void Uniform(Binding id, float v) = 0; + virtual void Uniform(Binding id, float v0, float v1, float v2, float v3) = 0; + virtual void Uniform(Binding id, const CVector3D& v) = 0; + virtual void Uniform(Binding id, const CColor& v) = 0; + virtual void Uniform(Binding id, const CMatrix3D& v) = 0; + +protected: + CShaderProgram(int streamflags); + + bool m_IsValid; + int m_StreamFlags; +}; + +typedef shared_ptr CShaderProgramPtr; + +#endif // INCLUDED_SHADERPROGRAM diff --git a/source/graphics/ShaderTechnique.cpp b/source/graphics/ShaderTechnique.cpp new file mode 100644 index 0000000000..683036c157 --- /dev/null +++ b/source/graphics/ShaderTechnique.cpp @@ -0,0 +1,134 @@ +/* Copyright (C) 2011 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#include "precompiled.h" + +#include "ShaderTechnique.h" + +CShaderPass::CShaderPass(const CShaderProgramPtr& shader) : + m_Shader(shader), + m_HasAlpha(false), m_HasBlend(false), m_HasColorMask(false), m_HasDepthMask(false) +{ +} + +void CShaderPass::Bind() +{ + m_Shader->Bind(); + + if (m_HasAlpha) + { + glEnable(GL_ALPHA_TEST); + glAlphaFunc(m_AlphaFunc, m_AlphaRef); + } + + if (m_HasBlend) + { + glEnable(GL_BLEND); + glBlendFunc(m_BlendSrc, m_BlendDst); + } + + if (m_HasColorMask) + glColorMask(m_ColorMaskR, m_ColorMaskG, m_ColorMaskB, m_ColorMaskA); + + if (m_HasDepthMask) + glDepthMask(m_DepthMask); +} + +void CShaderPass::Unbind() +{ + m_Shader->Unbind(); + + if (m_HasAlpha) + { + glDisable(GL_ALPHA_TEST); + } + + if (m_HasBlend) + { + glDisable(GL_BLEND); + } + + if (m_HasColorMask) + glColorMask(1, 1, 1, 1); + + if (m_HasDepthMask) + glDepthMask(1); +} + +void CShaderPass::AlphaFunc(GLenum func, GLclampf ref) +{ + m_HasAlpha = true; + m_AlphaFunc = func; + m_AlphaRef = ref; +} + +void CShaderPass::BlendFunc(GLenum src, GLenum dst) +{ + m_HasBlend = true; + m_BlendSrc = src; + m_BlendDst = dst; +} + +void CShaderPass::ColorMask(GLboolean r, GLboolean g, GLboolean b, GLboolean a) +{ + m_HasColorMask = true; + m_ColorMaskR = r; + m_ColorMaskG = g; + m_ColorMaskB = b; + m_ColorMaskA = a; +} + +void CShaderPass::DepthMask(GLboolean mask) +{ + m_HasDepthMask = true; + m_DepthMask = mask; +} + + +CShaderTechnique::CShaderTechnique(const CShaderPass& pass) +{ + m_Passes.push_back(pass); +} + +void CShaderTechnique::AddPass(const CShaderPass& pass) +{ + m_Passes.push_back(pass); +} + +int CShaderTechnique::GetNumPasses() +{ + return m_Passes.size(); +} + +void CShaderTechnique::BeginPass(int pass) +{ + debug_assert(0 <= pass && pass < (int)m_Passes.size()); + m_Passes[pass].Bind(); +} + +void CShaderTechnique::EndPass(int pass) +{ + debug_assert(0 <= pass && pass < (int)m_Passes.size()); + m_Passes[pass].Unbind(); +} + +CShaderProgramPtr CShaderTechnique::GetShader(int pass) +{ + debug_assert(0 <= pass && pass < (int)m_Passes.size()); + return m_Passes[pass].GetShader(); + +} diff --git a/source/graphics/ShaderTechnique.h b/source/graphics/ShaderTechnique.h new file mode 100644 index 0000000000..2176f988c0 --- /dev/null +++ b/source/graphics/ShaderTechnique.h @@ -0,0 +1,91 @@ +/* Copyright (C) 2011 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#ifndef INCLUDED_SHADERTECHNIQUE +#define INCLUDED_SHADERTECHNIQUE + +#include "graphics/ShaderProgram.h" + +/** + * Implements a render pass consisting of various GL state changes and a shader. + */ +class CShaderPass +{ +public: + CShaderPass(const CShaderProgramPtr& shader); + + // Add various bits of GL state to the pass: + void AlphaFunc(GLenum func, GLclampf ref); + void BlendFunc(GLenum src, GLenum dst); + void ColorMask(GLboolean r, GLboolean g, GLboolean b, GLboolean a); + void DepthMask(GLboolean mask); + + /** + * Set all the GL state that was previously specified on this pass. + */ + void Bind(); + + /** + * Reset the GL state to the default. + */ + void Unbind(); + + CShaderProgramPtr GetShader() { return m_Shader; } + +private: + CShaderProgramPtr m_Shader; + + bool m_HasAlpha; + GLenum m_AlphaFunc; + GLclampf m_AlphaRef; + + bool m_HasBlend; + GLenum m_BlendSrc; + GLenum m_BlendDst; + + bool m_HasColorMask; + GLboolean m_ColorMaskR; + GLboolean m_ColorMaskG; + GLboolean m_ColorMaskB; + GLboolean m_ColorMaskA; + + bool m_HasDepthMask; + GLboolean m_DepthMask; +}; + +/** + * Implements a render technique consisting of a sequence of passes. + * In theory these should probably be loaded from an XML file or something, + * but currently you have to construct them manually. + */ +class CShaderTechnique +{ +public: + CShaderTechnique(const CShaderPass& pass); + void AddPass(const CShaderPass& pass); + + int GetNumPasses(); + + void BeginPass(int pass); + void EndPass(int pass); + CShaderProgramPtr GetShader(int pass); + +private: + std::vector m_Passes; +}; + +#endif // INCLUDED_SHADERTECHNIQUE diff --git a/source/graphics/TextureManager.cpp b/source/graphics/TextureManager.cpp index b03e65cd1c..bd3a1010a3 100644 --- a/source/graphics/TextureManager.cpp +++ b/source/graphics/TextureManager.cpp @@ -512,6 +512,11 @@ CTexture::~CTexture() } void CTexture::Bind(size_t unit) +{ + ogl_tex_bind(GetHandle(), unit); +} + +Handle CTexture::GetHandle() { // TODO: TryLoad might call ogl_tex_upload which enables GL_TEXTURE_2D // on texture unit 0, regardless of 'unit', which callers might @@ -519,7 +524,7 @@ void CTexture::Bind(size_t unit) TryLoad(); - ogl_tex_bind(m_Handle, unit); + return m_Handle; } bool CTexture::TryLoad() diff --git a/source/graphics/TextureManager.h b/source/graphics/TextureManager.h index 2f8651a18e..897aaf94ec 100644 --- a/source/graphics/TextureManager.h +++ b/source/graphics/TextureManager.h @@ -225,6 +225,11 @@ public: */ void Bind(size_t unit = 0); + /** + * Returns a ogl_tex handle, for later binding. See comments from Bind(). + */ + Handle GetHandle(); + /** * Attempt to load the texture data quickly, as with Bind(). * Returns whether the texture data is currently loaded. diff --git a/source/lib/glext_funcs.h b/source/lib/glext_funcs.h index 30097bdfb0..9e37cdb164 100644 --- a/source/lib/glext_funcs.h +++ b/source/lib/glext_funcs.h @@ -114,7 +114,25 @@ FUNC(void, glGetFramebufferAttachmentParameterivEXT, (GLenum target, GLenum atta FUNC(void, glGenerateMipmapEXT, (GLenum target)) // GL_ARB_vertex_program, GL_ARB_fragment_program +FUNC(void, glProgramStringARB, (GLenum target, GLenum format, GLsizei len, const GLvoid *string)) +FUNC(void, glBindProgramARB, (GLenum target, GLuint program)) +FUNC(void, glDeleteProgramsARB, (GLsizei n, const GLuint *programs)) +FUNC(void, glGenProgramsARB, (GLsizei n, GLuint *programs)) +FUNC(void, glProgramEnvParameter4dARB, (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w)) +FUNC(void, glProgramEnvParameter4dvARB, (GLenum target, GLuint index, const GLdouble *params)) +FUNC(void, glProgramEnvParameter4fARB, (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w)) +FUNC(void, glProgramEnvParameter4fvARB, (GLenum target, GLuint index, const GLfloat *params)) +FUNC(void, glProgramLocalParameter4dARB, (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w)) +FUNC(void, glProgramLocalParameter4dvARB, (GLenum target, GLuint index, const GLdouble *params)) +FUNC(void, glProgramLocalParameter4fARB, (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w)) +FUNC(void, glProgramLocalParameter4fvARB, (GLenum target, GLuint index, const GLfloat *params)) +FUNC(void, glGetProgramEnvParameterdvARB, (GLenum target, GLuint index, GLdouble *params)) +FUNC(void, glGetProgramEnvParameterfvARB, (GLenum target, GLuint index, GLfloat *params)) +FUNC(void, glGetProgramLocalParameterdvARB, (GLenum target, GLuint index, GLdouble *params)) +FUNC(void, glGetProgramLocalParameterfvARB, (GLenum target, GLuint index, GLfloat *params)) FUNC(void, glGetProgramivARB, (GLenum target, GLenum pname, GLint *params)) +FUNC(void, glGetProgramStringARB, (GLenum target, GLenum pname, GLvoid *string)) +FUNC(GLboolean, glIsProgramARB, (GLuint program)) // GL_ARB_shader_objects // (NOTE: Many of these have "Object" in their ARB names, but "Program" or "Shader" in their core names. diff --git a/source/ps/Game.cpp b/source/ps/Game.cpp index 769522ca83..6f5d5a9051 100644 --- a/source/ps/Game.cpp +++ b/source/ps/Game.cpp @@ -279,14 +279,35 @@ void CGame::Interpolate(float frameLength) m_TurnManager->Interpolate(frameLength); } + static CColor BrokenColor(0.3f, 0.3f, 0.3f, 1.0f); -CColor CGame::GetPlayerColour(int player) const + +void CGame::CachePlayerColours() { + m_PlayerColours.clear(); + CmpPtr cmpPlayerManager(*m_Simulation2, SYSTEM_ENTITY); if (cmpPlayerManager.null()) - return BrokenColor; - CmpPtr cmpPlayer(*m_Simulation2, cmpPlayerManager->GetPlayerByID(player)); - if (cmpPlayer.null()) - return BrokenColor; - return cmpPlayer->GetColour(); + return; + + int numPlayers = cmpPlayerManager->GetNumPlayers(); + m_PlayerColours.resize(numPlayers); + + for (int i = 0; i < numPlayers; ++i) + { + CmpPtr cmpPlayer(*m_Simulation2, cmpPlayerManager->GetPlayerByID(i)); + if (cmpPlayer.null()) + m_PlayerColours[i] = BrokenColor; + else + m_PlayerColours[i] = cmpPlayer->GetColour(); + } +} + + +CColor CGame::GetPlayerColour(int player) const +{ + if (player < 0 || player >= (int)m_PlayerColours.size()) + return BrokenColor; + + return m_PlayerColours[player]; } diff --git a/source/ps/Game.h b/source/ps/Game.h index 082787b0a4..d8897a12e7 100644 --- a/source/ps/Game.h +++ b/source/ps/Game.h @@ -92,6 +92,14 @@ public: int GetPlayerID(); void SetPlayerID(int playerID); + /** + * Retrieving player colours from scripts is slow, so this updates an + * internal cache of all players' colours. + * Call this just before rendering, so it will always have the latest + * colours. + */ + void CachePlayerColours(); + CColor GetPlayerColour(int player) const; /** @@ -155,6 +163,8 @@ private: void RegisterInit(const CScriptValRooted& attribs); IReplayLogger* m_ReplayLogger; CScriptValRooted m_RegisteredAttribs; + + std::vector m_PlayerColours; }; extern CGame *g_Game; diff --git a/source/ps/GameSetup/GameSetup.cpp b/source/ps/GameSetup/GameSetup.cpp index 75e436a17b..c948bf5e6a 100644 --- a/source/ps/GameSetup/GameSetup.cpp +++ b/source/ps/GameSetup/GameSetup.cpp @@ -199,15 +199,17 @@ void Render() GUI::ParseString(skystring.FromUTF8(), skycol); g_Renderer.SetClearColor(skycol.AsSColor4ub()); + // prepare before starting the renderer frame + if (g_Game && g_Game->IsGameStarted()) + g_Game->GetView()->BeginFrame(); + // start new frame g_Renderer.BeginFrame(); ogl_WarnIfError(); if (g_Game && g_Game->IsGameStarted()) - { g_Game->GetView()->Render(); - } ogl_WarnIfError(); diff --git a/source/ps/XML/RelaxNG.cpp b/source/ps/XML/RelaxNG.cpp index 5173b75662..2088ea336e 100644 --- a/source/ps/XML/RelaxNG.cpp +++ b/source/ps/XML/RelaxNG.cpp @@ -58,6 +58,13 @@ bool RelaxNGValidator::LoadGrammar(const std::string& grammar) } bool RelaxNGValidator::Validate(const std::wstring& filename, const std::wstring& document) +{ + std::string docutf8 = "" + utf8_from_wstring(document); + + return ValidateEncoded(filename, docutf8); +} + +bool RelaxNGValidator::ValidateEncoded(const std::wstring& filename, const std::string& document) { TIMER_ACCRUE(xml_validation); @@ -67,9 +74,7 @@ bool RelaxNGValidator::Validate(const std::wstring& filename, const std::wstring return false; } - std::string docutf8 = "" + utf8_from_wstring(document); - - xmlDocPtr doc = xmlReadMemory(docutf8.c_str(), (int)docutf8.size(), utf8_from_wstring(filename).c_str(), NULL, XML_PARSE_NONET); + xmlDocPtr doc = xmlReadMemory(document.c_str(), (int)document.size(), utf8_from_wstring(filename).c_str(), NULL, XML_PARSE_NONET); if (doc == NULL) { LOGERROR(L"RelaxNGValidator: Failed to parse document"); diff --git a/source/ps/XML/RelaxNG.h b/source/ps/XML/RelaxNG.h index b33b34b4e2..18758192b2 100644 --- a/source/ps/XML/RelaxNG.h +++ b/source/ps/XML/RelaxNG.h @@ -31,6 +31,8 @@ public: bool Validate(const std::wstring& filename, const std::wstring& document); + bool ValidateEncoded(const std::wstring& filename, const std::string& document); + private: xmlRelaxNGPtr m_Schema; }; diff --git a/source/ps/XML/XMLWriter.cpp b/source/ps/XML/XMLWriter.cpp index 0947a50a8b..8182b7f608 100644 --- a/source/ps/XML/XMLWriter.cpp +++ b/source/ps/XML/XMLWriter.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2009 Wildfire Games. +/* Copyright (C) 2011 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -21,6 +21,7 @@ #include "ps/CLogger.h" #include "ps/Filesystem.h" +#include "ps/XML/Xeromyces.h" #include "lib/utf8.h" #include "lib/sysdep/cpu.h" #include "maths/Fixed.h" @@ -111,6 +112,22 @@ const CStr& XMLWriter_File::GetOutput() } +void XMLWriter_File::XMB(const XMBFile& file) +{ + ElementXMB(file, file.GetRoot()); +} + +void XMLWriter_File::ElementXMB(const XMBFile& file, XMBElement el) +{ + XMLWriter_Element writer(*this, file.GetElementString(el.GetNodeName()).c_str()); + + XERO_ITER_ATTR(el, attr) + writer.Attribute(file.GetAttributeString(attr.Name).c_str(), attr.Value); + + XERO_ITER_EL(el, child) + ElementXMB(file, child); +} + void XMLWriter_File::Comment(const char* text) { ElementStart(NULL, "!-- "); diff --git a/source/ps/XML/XMLWriter.h b/source/ps/XML/XMLWriter.h index 55ad4530fe..b07f9a58b7 100644 --- a/source/ps/XML/XMLWriter.h +++ b/source/ps/XML/XMLWriter.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2009 Wildfire Games. +/* Copyright (C) 2011 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -91,6 +91,8 @@ end of XMLWriter.cpp. // Add a 'setting': value #define XML_Setting(name, value) xml_element_.Setting(name, value) +#define XML_WriteXMB(xero) xml_file_.XMB(xero) + // Create a VFS file from the XML data. // Returns true on success, false (and logs an error) on failure. #define XML_StoreVFS(vfs, pathname) xml_file_.StoreVFS(vfs, pathname) @@ -103,6 +105,8 @@ end of XMLWriter.cpp. #include "ps/CStr.h" #include "lib/file/vfs/vfs.h" +class XMBElement; +class XMBFile; class XMLWriter_Element; class XMLWriter_File @@ -114,6 +118,8 @@ public: void Comment(const char* text); + void XMB(const XMBFile& file); + bool StoreVFS(const PIVFS& vfs, const VfsPath& pathname); const CStr& GetOutput(); @@ -121,6 +127,8 @@ private: friend class XMLWriter_Element; + void ElementXMB(const XMBFile& file, XMBElement el); + void ElementStart(XMLWriter_Element* element, const char* name); void ElementText(const char* text, bool cdata); template void ElementAttribute(const char* name, const T& value, bool newelement); diff --git a/source/renderer/DecalRData.cpp b/source/renderer/DecalRData.cpp index dde2d1066f..16a34b45eb 100644 --- a/source/renderer/DecalRData.cpp +++ b/source/renderer/DecalRData.cpp @@ -20,6 +20,7 @@ #include "DecalRData.h" #include "graphics/Decal.h" +#include "graphics/LightEnv.h" #include "graphics/Model.h" #include "graphics/Terrain.h" #include "graphics/TextureManager.h" @@ -41,6 +42,10 @@ CDecalRData::CDecalRData(CModelDecal* decal) m_Position.elems = 3; m_Array.AddAttribute(&m_Position); + m_DiffuseColor.type = GL_UNSIGNED_BYTE; + m_DiffuseColor.elems = 4; + m_Array.AddAttribute(&m_DiffuseColor); + m_UV.type = GL_FLOAT; m_UV.elems = 2; m_Array.AddAttribute(&m_UV); @@ -81,9 +86,12 @@ void CDecalRData::Render() u8* indexBase = m_IndexArray.Bind(); + // TODO: make the shading color available to shader-based rendering + // (which uses the color array) too glColor3fv(m_Decal->GetShadingColor().FloatArray()); glVertexPointer(3, GL_FLOAT, stride, base + m_Position.offset); + glColorPointer(4, GL_UNSIGNED_BYTE, stride, base + m_DiffuseColor.offset); glTexCoordPointer(2, GL_FLOAT, stride, base + m_UV.offset); if (!g_Renderer.m_SkipSubmit) @@ -118,8 +126,11 @@ void CDecalRData::BuildArrays() m_Array.SetNumVertices((i1-i0+1)*(j1-j0+1)); m_Array.Layout(); VertexArrayIterator Position = m_Position.GetIterator(); + VertexArrayIterator DiffuseColor = m_DiffuseColor.GetIterator(); VertexArrayIterator UV = m_UV.GetIterator(); + bool includeSunColor = (g_Renderer.GetRenderPath() != CRenderer::RP_SHADER); + for (ssize_t j = j0; j <= j1; ++j) { for (ssize_t i = i0; i <= i1; ++i) @@ -133,6 +144,11 @@ void CDecalRData::BuildArrays() *Position = pos; ++Position; + CVector3D normal; + m_Decal->m_Terrain->CalcNormal(i, j, normal); + *DiffuseColor = g_Renderer.GetLightEnv().EvaluateDiffuse(normal, includeSunColor); + ++DiffuseColor; + // Map from world space back into decal texture space CVector3D inv = m_Decal->GetInvTransform().Transform(pos); (*UV)[0] = 0.5f + (inv.X - decal.m_OffsetX) / decal.m_SizeX; diff --git a/source/renderer/DecalRData.h b/source/renderer/DecalRData.h index e0dd13cb9f..347509c4af 100644 --- a/source/renderer/DecalRData.h +++ b/source/renderer/DecalRData.h @@ -40,6 +40,7 @@ private: VertexArray m_Array; VertexArray::Attribute m_Position; + VertexArray::Attribute m_DiffuseColor; VertexArray::Attribute m_UV; CModelDecal* m_Decal; diff --git a/source/renderer/HWLightingModelRenderer.cpp b/source/renderer/HWLightingModelRenderer.cpp index 953f1013a1..fc26945ab8 100644 --- a/source/renderer/HWLightingModelRenderer.cpp +++ b/source/renderer/HWLightingModelRenderer.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2009 Wildfire Games. +/* Copyright (C) 2011 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -26,8 +26,6 @@ #include "maths/Vector3D.h" #include "maths/Vector4D.h" -#include "ps/CLogger.h" - #include "graphics/Color.h" #include "graphics/LightEnv.h" #include "graphics/Model.h" @@ -81,9 +79,6 @@ struct HWLModel struct HWLightingModelRendererInternals { - /// Currently used RenderModifier - RenderModifierPtr modifier; - /// Previously prepared modeldef HWLModelDef* hwlmodeldef; @@ -331,4 +326,71 @@ void HWLightingModelRenderer::RenderModel(int streamflags, CModel* model, void* g_Renderer.m_Stats.m_ModelTris += numFaces; } +/////////////////////////////////////////////////////////////////////////////////////////////// +// ShaderModelRenderer implementation +ShaderModelRenderer::ShaderModelRenderer() : + HWLightingModelRenderer(false) +{ +} + +void ShaderModelRenderer::BeginPass(int streamflags, const CMatrix3D* UNUSED(texturematrix)) +{ + debug_assert(streamflags == (streamflags & (STREAM_POS|STREAM_NORMAL|STREAM_UV0))); + + if (streamflags & STREAM_POS) + glEnableClientState(GL_VERTEX_ARRAY); + + if (streamflags & STREAM_NORMAL) + glEnableClientState(GL_NORMAL_ARRAY); + + if (streamflags & STREAM_UV0) + glEnableClientState(GL_TEXTURE_COORD_ARRAY); +} + +void ShaderModelRenderer::EndPass(int streamflags) +{ + if (streamflags & STREAM_POS) + glDisableClientState(GL_VERTEX_ARRAY); + + if (streamflags & STREAM_NORMAL) + glDisableClientState(GL_NORMAL_ARRAY); + + if (streamflags & STREAM_UV0) + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + + CVertexBuffer::Unbind(); +} + +void ShaderModelRenderer::RenderModel(int streamflags, CModel* model, void* data) +{ + CModelDefPtr mdldef = model->GetModelDef(); + HWLModel* hwlmodel = (HWLModel*)data; + + u8* base = hwlmodel->m_Array.Bind(); + GLsizei stride = (GLsizei)hwlmodel->m_Array.GetStride(); + + u8* indexBase = m->hwlmodeldef->m_IndexArray.Bind(); + + if (streamflags & STREAM_POS) + glVertexPointer(3, GL_FLOAT, stride, base + hwlmodel->m_Position.offset); + + if (streamflags & STREAM_NORMAL) + glNormalPointer(GL_FLOAT, stride, base + hwlmodel->m_Normal.offset); + + if (streamflags & STREAM_UV0) + glTexCoordPointer(2, GL_FLOAT, stride, base + hwlmodel->m_UV.offset); + + // render the lot + size_t numFaces = mdldef->GetNumFaces(); + + if (!g_Renderer.m_SkipSubmit) + { + pglDrawRangeElementsEXT(GL_TRIANGLES, 0, (GLuint)mdldef->GetNumVertices()-1, + (GLsizei)numFaces*3, GL_UNSIGNED_SHORT, indexBase); + } + + // bump stats + g_Renderer.m_Stats.m_DrawCalls++; + g_Renderer.m_Stats.m_ModelTris += numFaces; +} diff --git a/source/renderer/HWLightingModelRenderer.h b/source/renderer/HWLightingModelRenderer.h index c313e36417..84e1d19dee 100644 --- a/source/renderer/HWLightingModelRenderer.h +++ b/source/renderer/HWLightingModelRenderer.h @@ -68,9 +68,24 @@ public: */ static bool IsAvailable(); -private: +protected: HWLightingModelRendererInternals* m; }; +/** + * Render animated models using a ShaderRenderModifier. + * This just passes through the vertex data directly; the modifier is responsible + * for setting any shader uniforms etc. + */ +class ShaderModelRenderer : public HWLightingModelRenderer +{ +public: + ShaderModelRenderer(); + + void BeginPass(int streamflags, const CMatrix3D* texturematrix); + void EndPass(int streamflags); + void RenderModel(int streamflags, CModel* model, void* data); +}; + #endif // INCLUDED_HWLIGHTINGMODELRENDERER diff --git a/source/renderer/InstancingModelRenderer.cpp b/source/renderer/InstancingModelRenderer.cpp index cdacc7102c..bb40cd15af 100644 --- a/source/renderer/InstancingModelRenderer.cpp +++ b/source/renderer/InstancingModelRenderer.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2009 Wildfire Games. +/* Copyright (C) 2011 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -289,4 +289,78 @@ void InstancingModelRenderer::RenderModel(int streamflags, CModel* model, void* g_Renderer.m_Stats.m_ModelTris += numFaces; } +/////////////////////////////////////////////////////////////////////////////////////////////// +// ShaderInstancingModelRenderer implementation +ShaderInstancingModelRenderer::ShaderInstancingModelRenderer() : + InstancingModelRenderer(false) +{ +} + +void ShaderInstancingModelRenderer::BeginPass(int streamflags, const CMatrix3D* UNUSED(texturematrix)) +{ + debug_assert(streamflags == (streamflags & (STREAM_POS|STREAM_NORMAL|STREAM_UV0))); + + if (streamflags & STREAM_POS) + glEnableClientState(GL_VERTEX_ARRAY); + + if (streamflags & STREAM_NORMAL) + glEnableClientState(GL_NORMAL_ARRAY); + + if (streamflags & STREAM_UV0) + glEnableClientState(GL_TEXTURE_COORD_ARRAY); +} + +void ShaderInstancingModelRenderer::EndPass(int streamflags) +{ + if (streamflags & STREAM_POS) + glDisableClientState(GL_VERTEX_ARRAY); + + if (streamflags & STREAM_NORMAL) + glDisableClientState(GL_NORMAL_ARRAY); + + if (streamflags & STREAM_UV0) + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + + CVertexBuffer::Unbind(); +} + +void ShaderInstancingModelRenderer::PrepareModelDef(int streamflags, const CModelDefPtr& def) +{ + m->imodeldef = (IModelDef*)def->GetRenderData(m); + + debug_assert(m->imodeldef); + + u8* base = m->imodeldef->m_Array.Bind(); + GLsizei stride = (GLsizei)m->imodeldef->m_Array.GetStride(); + + m->imodeldefIndexBase = m->imodeldef->m_IndexArray.Bind(); + + if (streamflags & STREAM_POS) + glVertexPointer(3, GL_FLOAT, stride, base + m->imodeldef->m_Position.offset); + + if (streamflags & STREAM_NORMAL) + glNormalPointer(GL_FLOAT, stride, base + m->imodeldef->m_Normal.offset); + + if (streamflags & STREAM_UV0) + glTexCoordPointer(2, GL_FLOAT, stride, base + m->imodeldef->m_UV.offset); +} + +void ShaderInstancingModelRenderer::RenderModel(int UNUSED(streamflags), CModel* model, void* UNUSED(data)) +{ + CModelDefPtr mdldef = model->GetModelDef(); + + // render the lot + size_t numFaces = mdldef->GetNumFaces(); + + if (!g_Renderer.m_SkipSubmit) + { + pglDrawRangeElementsEXT(GL_TRIANGLES, 0, (GLuint)mdldef->GetNumVertices()-1, + (GLsizei)numFaces*3, GL_UNSIGNED_SHORT, m->imodeldefIndexBase); + } + + // bump stats + g_Renderer.m_Stats.m_DrawCalls++; + g_Renderer.m_Stats.m_ModelTris += numFaces; + +} diff --git a/source/renderer/InstancingModelRenderer.h b/source/renderer/InstancingModelRenderer.h index bc850b153d..34e540a079 100644 --- a/source/renderer/InstancingModelRenderer.h +++ b/source/renderer/InstancingModelRenderer.h @@ -68,9 +68,24 @@ public: */ static bool IsAvailable(); -private: +protected: InstancingModelRendererInternals* m; }; +/** + * Render non-animated (but potentially moving) models using a ShaderRenderModifier. + * This just passes through the vertex data directly; the modifier is responsible + * for setting any shader uniforms etc (including the instancing transform). + */ +class ShaderInstancingModelRenderer : public InstancingModelRenderer +{ +public: + ShaderInstancingModelRenderer(); + + void BeginPass(int streamflags, const CMatrix3D* texturematrix); + void EndPass(int streamflags); + void PrepareModelDef(int streamflags, const CModelDefPtr& def); + void RenderModel(int streamflags, CModel* model, void* data); +}; #endif // INCLUDED_INSTANCINGMODELRENDERER diff --git a/source/renderer/PatchRData.cpp b/source/renderer/PatchRData.cpp index fe65a8a712..b7e56144e1 100644 --- a/source/renderer/PatchRData.cpp +++ b/source/renderer/PatchRData.cpp @@ -358,28 +358,41 @@ void CPatchRData::AddBlend(std::vector& blendVertices, std::vector SBlendVertex dst; + const CLightEnv& lightEnv = g_Renderer.GetLightEnv(); + CVector3D normal; + + bool includeSunColor = (g_Renderer.GetRenderPath() != CRenderer::RP_SHADER); + size_t index = blendVertices.size(); CalculateUV(dst.m_UVs, gx, gz); terrain->CalcPosition(gx, gz, dst.m_Position); + terrain->CalcNormal(gx, gz, normal); + dst.m_DiffuseColor = lightEnv.EvaluateDiffuse(normal, includeSunColor); dst.m_AlphaUVs[0] = vtx[0].m_AlphaUVs[0]; dst.m_AlphaUVs[1] = vtx[0].m_AlphaUVs[1]; blendVertices.push_back(dst); CalculateUV(dst.m_UVs, gx + 1, gz); terrain->CalcPosition(gx + 1, gz, dst.m_Position); + terrain->CalcNormal(gx + 1, gz, normal); + dst.m_DiffuseColor = lightEnv.EvaluateDiffuse(normal, includeSunColor); dst.m_AlphaUVs[0] = vtx[1].m_AlphaUVs[0]; dst.m_AlphaUVs[1] = vtx[1].m_AlphaUVs[1]; blendVertices.push_back(dst); CalculateUV(dst.m_UVs, gx + 1, gz + 1); terrain->CalcPosition(gx + 1, gz + 1, dst.m_Position); + terrain->CalcNormal(gx + 1, gz + 1, normal); + dst.m_DiffuseColor = lightEnv.EvaluateDiffuse(normal, includeSunColor); dst.m_AlphaUVs[0] = vtx[2].m_AlphaUVs[0]; dst.m_AlphaUVs[1] = vtx[2].m_AlphaUVs[1]; blendVertices.push_back(dst); CalculateUV(dst.m_UVs, gx, gz + 1); terrain->CalcPosition(gx, gz + 1, dst.m_Position); + terrain->CalcNormal(gx, gz + 1, normal); + dst.m_DiffuseColor = lightEnv.EvaluateDiffuse(normal, includeSunColor); dst.m_AlphaUVs[0] = vtx[3].m_AlphaUVs[0]; dst.m_AlphaUVs[1] = vtx[3].m_AlphaUVs[1]; blendVertices.push_back(dst); @@ -516,6 +529,8 @@ void CPatchRData::BuildVertices() CTerrain* terrain=m_Patch->m_Parent; const CLightEnv& lightEnv = g_Renderer.GetLightEnv(); + bool includeSunColor = (g_Renderer.GetRenderPath() != CRenderer::RP_SHADER); + // build vertices for (ssize_t j=0;jCalcNormal(ix,iz,normal); - RGBColor diffuse; - lightEnv.EvaluateDirect(normal, diffuse); - vertices[v].m_DiffuseColor = ConvertRGBColorTo4ub(diffuse); + vertices[v].m_DiffuseColor = lightEnv.EvaluateDiffuse(normal, includeSunColor); } } @@ -836,6 +849,8 @@ void CPatchRData::RenderBlends(const std::vector& patches) glVertexPointer(3, GL_FLOAT, stride, &base->m_Position[0]); + glColorPointer(4, GL_UNSIGNED_BYTE, stride, &base->m_DiffuseColor); + pglClientActiveTextureARB(GL_TEXTURE0); glTexCoordPointer(2, GL_FLOAT, stride, &base->m_UVs[0]); diff --git a/source/renderer/PatchRData.h b/source/renderer/PatchRData.h index da0eece5c1..7b8e381024 100644 --- a/source/renderer/PatchRData.h +++ b/source/renderer/PatchRData.h @@ -82,12 +82,12 @@ private: struct SBlendVertex { // vertex position CVector3D m_Position; + // diffuse color from sunlight + SColor4ub m_DiffuseColor; // vertex uvs for base texture float m_UVs[2]; // vertex uvs for alpha texture float m_AlphaUVs[2]; - // add some padding - u32 m_Padding[1]; }; cassert(sizeof(SBlendVertex) == 32); diff --git a/source/renderer/RenderModifiers.cpp b/source/renderer/RenderModifiers.cpp index 4d541e9457..f4df4033fe 100644 --- a/source/renderer/RenderModifiers.cpp +++ b/source/renderer/RenderModifiers.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2009 Wildfire Games. +/* Copyright (C) 2011 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -24,10 +24,13 @@ #include "lib/ogl.h" #include "maths/Vector3D.h" #include "maths/Vector4D.h" - #include "maths/Matrix3D.h" +#include "ps/Game.h" + +#include "graphics/GameView.h" #include "graphics/LightEnv.h" +#include "graphics/LOSTexture.h" #include "graphics/Model.h" #include "graphics/TextureManager.h" @@ -35,7 +38,7 @@ #include "renderer/Renderer.h" #include "renderer/ShadowMap.h" - +#include /////////////////////////////////////////////////////////////////////////////////////////////// // RenderModifier implementation @@ -299,3 +302,82 @@ void SolidColorRenderModifier::PrepareTexture(int UNUSED(pass), CTexturePtr& UNU void SolidColorRenderModifier::PrepareModel(int UNUSED(pass), CModel* UNUSED(model)) { } + + +/////////////////////////////////////////////////////////////////////////////////////////////// +// ShaderRenderModifier implementation + +ShaderRenderModifier::ShaderRenderModifier(const CShaderTechnique& technique) : + m_Technique(technique) +{ +} + +ShaderRenderModifier::~ShaderRenderModifier() +{ +} + +int ShaderRenderModifier::BeginPass(int pass) +{ + m_Technique.BeginPass(pass); + + CShaderProgramPtr shader = m_Technique.GetShader(pass); + + if (GetShadowMap() && shader->HasTexture("shadowTex")) + { + shader->BindTexture("shadowTex", GetShadowMap()->GetTexture()); + shader->Uniform("shadowTransform", GetShadowMap()->GetTextureMatrix()); + } + + if (GetLightEnv()) + { + shader->Uniform("ambient", GetLightEnv()->m_UnitsAmbientColor); + shader->Uniform("sunDir", GetLightEnv()->GetSunDir()); + shader->Uniform("sunColor", GetLightEnv()->m_SunColor); + } + + if (shader->HasTexture("losTex")) + { + CLOSTexture& los = g_Game->GetView()->GetLOSTexture(); + shader->BindTexture("losTex", los.GetTexture()); + // Don't bother sending the whole matrix, we just need two floats (scale and translation) + shader->Uniform("losTransform", los.GetTextureMatrix()[0], los.GetTextureMatrix()[12], 0.f, 0.f); + } + + m_BindingInstancingTransform = shader->GetUniformBinding("instancingTransform"); + m_BindingShadingColor = shader->GetUniformBinding("shadingColor"); + m_BindingObjectColor = shader->GetUniformBinding("objectColor"); + m_BindingPlayerColor = shader->GetUniformBinding("playerColor"); + + return shader->GetStreamFlags(); +} + +bool ShaderRenderModifier::EndPass(int pass) +{ + m_Technique.EndPass(pass); + + return (pass >= m_Technique.GetNumPasses()-1); +} + +void ShaderRenderModifier::PrepareTexture(int pass, CTexturePtr& texture) +{ + CShaderProgramPtr shader = m_Technique.GetShader(pass); + + shader->BindTexture("baseTex", texture->GetHandle()); +} + +void ShaderRenderModifier::PrepareModel(int pass, CModel* model) +{ + CShaderProgramPtr shader = m_Technique.GetShader(pass); + + if (m_BindingInstancingTransform.Active()) + shader->Uniform(m_BindingInstancingTransform, model->GetTransform()); + + if (m_BindingShadingColor.Active()) + shader->Uniform(m_BindingShadingColor, model->GetShadingColor()); + + if (m_BindingObjectColor.Active()) + shader->Uniform(m_BindingObjectColor, model->GetMaterial().GetObjectColor()); + + if (m_BindingPlayerColor.Active()) + shader->Uniform(m_BindingPlayerColor, model->GetMaterial().GetPlayerColor()); +} diff --git a/source/renderer/RenderModifiers.h b/source/renderer/RenderModifiers.h index 1e94294c37..fe5fa5014e 100644 --- a/source/renderer/RenderModifiers.h +++ b/source/renderer/RenderModifiers.h @@ -25,6 +25,7 @@ #define INCLUDED_RENDERMODIFIERS #include "ModelRenderer.h" +#include "graphics/ShaderTechnique.h" #include "graphics/Texture.h" class CLightEnv; @@ -230,4 +231,28 @@ public: void PrepareModel(int pass, CModel* model); }; +/** + * A RenderModifier that can be used with any CShaderTechnique. + * Uniforms and textures get set appropriately. + */ +class ShaderRenderModifier : public LitRenderModifier +{ +public: + ShaderRenderModifier(const CShaderTechnique& technique); + ~ShaderRenderModifier(); + + // Implementation + int BeginPass(int pass); + bool EndPass(int pass); + void PrepareTexture(int pass, CTexturePtr& texture); + void PrepareModel(int pass, CModel* model); + +private: + CShaderTechnique m_Technique; + CShaderProgram::Binding m_BindingInstancingTransform; + CShaderProgram::Binding m_BindingShadingColor; + CShaderProgram::Binding m_BindingObjectColor; + CShaderProgram::Binding m_BindingPlayerColor; +}; + #endif // INCLUDED_RENDERMODIFIERS diff --git a/source/renderer/Renderer.cpp b/source/renderer/Renderer.cpp index 43033b87e7..013d474bd7 100644 --- a/source/renderer/Renderer.cpp +++ b/source/renderer/Renderer.cpp @@ -42,15 +42,17 @@ #include "ps/Loader.h" #include "ps/ProfileViewer.h" #include "graphics/Camera.h" -#include "graphics/Texture.h" -#include "graphics/TextureManager.h" +#include "graphics/DefaultEmitter.h" +#include "graphics/GameView.h" #include "graphics/LightEnv.h" -#include "graphics/Terrain.h" #include "graphics/Model.h" #include "graphics/ModelDef.h" -#include "graphics/GameView.h" #include "graphics/ParticleEngine.h" -#include "graphics/DefaultEmitter.h" +#include "graphics/ShaderManager.h" +#include "graphics/ShaderTechnique.h" +#include "graphics/Terrain.h" +#include "graphics/Texture.h" +#include "graphics/TextureManager.h" #include "renderer/FixedFunctionModelRenderer.h" #include "renderer/HWLightingModelRenderer.h" #include "renderer/InstancingModelRenderer.h" @@ -222,9 +224,15 @@ public: /// true if CRenderer::Open has been called bool IsOpen; + /// true if shaders need to be reloaded + bool ShadersDirty; + /// Table to display renderer stats in-game via profile system CRendererStatsTable profileTable; + /// Shader manager + CShaderManager shaderManager; + /// Water manager WaterManager waterManager; @@ -266,10 +274,18 @@ public: ModelRenderer* pal_TranspHWLit[NumVertexTypes]; ModelRenderer* pal_TranspSortAll; + ModelRenderer* pal_NormalShader; + ModelRenderer* pal_NormalInstancingShader; + ModelRenderer* pal_PlayerShader; + ModelRenderer* pal_PlayerInstancingShader; + ModelRenderer* pal_TranspShader; + ModelVertexRendererPtr VertexFF[NumVertexTypes]; ModelVertexRendererPtr VertexHWLit[NumVertexTypes]; ModelVertexRendererPtr VertexInstancing[NumVertexTypes]; ModelVertexRendererPtr VertexPolygonSort; + ModelVertexRendererPtr VertexRendererShader; + ModelVertexRendererPtr VertexInstancingShader; // generic RenderModifiers that are supposed to be used directly RenderModifierPtr ModWireframe; @@ -280,7 +296,13 @@ public: // RenderModifiers that are selected from the palette below RenderModifierPtr ModNormal; + RenderModifierPtr ModNormalInstancing; RenderModifierPtr ModPlayer; + RenderModifierPtr ModPlayerInstancing; + RenderModifierPtr ModSolid; + RenderModifierPtr ModSolidInstancing; + RenderModifierPtr ModSolidPlayer; + RenderModifierPtr ModSolidPlayerInstancing; RenderModifierPtr ModTransparent; // Palette of available RenderModifiers @@ -290,11 +312,23 @@ public: LitRenderModifierPtr ModPlayerLit; RenderModifierPtr ModTransparentUnlit; LitRenderModifierPtr ModTransparentLit; + + RenderModifierPtr ModShaderSolidColor; + RenderModifierPtr ModShaderSolidColorInstancing; + RenderModifierPtr ModShaderSolidPlayerColor; + RenderModifierPtr ModShaderSolidPlayerColorInstancing; + RenderModifierPtr ModShaderSolidTex; + LitRenderModifierPtr ModShaderNormal; + LitRenderModifierPtr ModShaderNormalInstancing; + LitRenderModifierPtr ModShaderPlayer; + LitRenderModifierPtr ModShaderPlayerInstancing; + LitRenderModifierPtr ModShaderTransparent; + RenderModifierPtr ModShaderTransparentShadow; } Model; CRendererInternals() - : IsOpen(false), profileTable(g_Renderer.m_Stats), textureManager(g_VFS, false, false) + : IsOpen(false), ShadersDirty(true), profileTable(g_Renderer.m_Stats), textureManager(g_VFS, false, false) { terrainRenderer = new TerrainRenderer(); shadow = new ShadowMap(); @@ -312,6 +346,12 @@ public: } Model.pal_TranspSortAll = 0; + Model.pal_NormalShader = 0; + Model.pal_NormalInstancingShader = 0; + Model.pal_PlayerShader = 0; + Model.pal_PlayerInstancingShader = 0; + Model.pal_TranspShader = 0; + Model.Normal = 0; Model.NormalInstancing = 0; Model.Player = 0; @@ -360,15 +400,18 @@ public: /** * Renders all non-transparent models with the given modifiers. */ - void CallModelRenderers(const RenderModifierPtr& modNormal, const RenderModifierPtr& modPlayer, int flags) + void CallModelRenderers( + const RenderModifierPtr& modNormal, const RenderModifierPtr& modNormalInstancing, + const RenderModifierPtr& modPlayer, const RenderModifierPtr& modPlayerInstancing, + int flags) { Model.Normal->Render(modNormal, flags); if (Model.Normal != Model.NormalInstancing) - Model.NormalInstancing->Render(modNormal, flags); + Model.NormalInstancing->Render(modNormalInstancing, flags); Model.Player->Render(modPlayer, flags); if (Model.Player != Model.PlayerInstancing) - Model.PlayerInstancing->Render(modPlayer, flags); + Model.PlayerInstancing->Render(modPlayerInstancing, flags); } }; @@ -403,13 +446,15 @@ CRenderer::CRenderer() m_Options.m_FancyWater = false; m_Options.m_Shadows = false; m_Options.m_ShadowAlphaFix = true; + m_Options.m_ARBProgramShadow = true; m_ShadowZBias = 0.02f; m_ShadowMapSize = 0; + m_LightEnv = NULL; + m_hCompositeAlphaMap = 0; - AddLocalProperty(L"shadows", &m_Options.m_Shadows, false); AddLocalProperty(L"fancyWater", &m_Options.m_FancyWater, false); AddLocalProperty(L"horizonHeight", &m->skyManager.m_HorizonHeight, false); AddLocalProperty(L"waterMurkiness", &m->waterManager.m_Murkiness, false); @@ -442,6 +487,12 @@ CRenderer::~CRenderer() } delete m->Model.pal_TranspSortAll; + delete m->Model.pal_NormalShader; + delete m->Model.pal_NormalInstancingShader; + delete m->Model.pal_PlayerShader; + delete m->Model.pal_PlayerInstancingShader; + delete m->Model.pal_TranspShader; + // general delete m_VertexShader; m_VertexShader = 0; @@ -461,6 +512,8 @@ void CRenderer::EnumCaps() { // assume support for nothing m_Caps.m_VBO = false; + m_Caps.m_ARBProgram = false; + m_Caps.m_ARBProgramShadow = false; m_Caps.m_VertexShader = false; m_Caps.m_FragmentShader = false; m_Caps.m_Shadows = false; @@ -474,12 +527,19 @@ void CRenderer::EnumCaps() } } + if (0 == ogl_HaveExtensions(0, "GL_ARB_vertex_program", "GL_ARB_fragment_program", NULL)) + { + m_Caps.m_ARBProgram = true; + if (ogl_HaveExtension("GL_ARB_fragment_program_shadow")) + m_Caps.m_ARBProgramShadow = true; + } + if (0 == ogl_HaveExtensions(0, "GL_ARB_shader_objects", "GL_ARB_shading_language_100", NULL)) { if (ogl_HaveExtension("GL_ARB_vertex_shader")) - m_Caps.m_VertexShader=true; + m_Caps.m_VertexShader = true; if (ogl_HaveExtension("GL_ARB_fragment_shader")) - m_Caps.m_FragmentShader=true; + m_Caps.m_FragmentShader = true; } if (ogl_max_tex_units >= 3) @@ -507,6 +567,74 @@ void CRenderer::EnumCaps() } } +void CRenderer::ReloadShaders() +{ + typedef std::map Defines; + + Defines defNull; + + Defines defBasic; + if (m_Options.m_Shadows) + { + defBasic["USE_SHADOW"] = "1"; + if (m_Caps.m_ARBProgramShadow && m_Options.m_ARBProgramShadow) + defBasic["USE_FP_SHADOW"] = "1"; + } + + if (m_LightEnv) + defBasic["LIGHTING_MODEL_" + m_LightEnv->GetLightingModel()] = "1"; + + Defines defColored = defBasic; + defColored["USE_OBJECTCOLOR"] = "1"; + + Defines defTransparent = defBasic; + defTransparent["USE_TRANSPARENT"] = "1"; + + // TODO: it'd be nicer to load this technique from an XML file or something + CShaderPass passTransparent0(m->shaderManager.LoadProgram("solid_tex", defNull)); + passTransparent0.AlphaFunc(GL_GREATER, 0.975f); + passTransparent0.ColorMask(0, 0, 0, 0); + CShaderPass passTransparent1(m->shaderManager.LoadProgram("model_common", defTransparent)); + passTransparent1.AlphaFunc(GL_GREATER, 0.0f); + passTransparent1.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + passTransparent1.DepthMask(0); + CShaderTechnique techTransparent(passTransparent0); + techTransparent.AddPass(passTransparent1); + + CShaderPass passTransparentShadow(m->shaderManager.LoadProgram("solid_tex", defBasic)); + passTransparentShadow.AlphaFunc(GL_GREATER, 0.4f); + CShaderTechnique techTransparentShadow(passTransparentShadow); + + m->Model.ModShaderSolidColor = RenderModifierPtr(new ShaderRenderModifier(CShaderTechnique(m->shaderManager.LoadProgram( + "solid", defNull)))); + m->Model.ModShaderSolidColorInstancing = RenderModifierPtr(new ShaderRenderModifier(CShaderTechnique(m->shaderManager.LoadProgram( + "solid_instancing", defNull)))); + + m->Model.ModShaderSolidPlayerColor = RenderModifierPtr(new ShaderRenderModifier(CShaderTechnique(m->shaderManager.LoadProgram( + "solid_player", defNull)))); + m->Model.ModShaderSolidPlayerColorInstancing = RenderModifierPtr(new ShaderRenderModifier(CShaderTechnique(m->shaderManager.LoadProgram( + "solid_player_instancing", defNull)))); + + m->Model.ModShaderSolidTex = RenderModifierPtr(new ShaderRenderModifier(CShaderTechnique(m->shaderManager.LoadProgram( + "solid_tex", defNull)))); + + m->Model.ModShaderNormal = LitRenderModifierPtr(new ShaderRenderModifier(CShaderTechnique(m->shaderManager.LoadProgram( + "model_common", defBasic)))); + m->Model.ModShaderNormalInstancing = LitRenderModifierPtr(new ShaderRenderModifier(CShaderTechnique(m->shaderManager.LoadProgram( + "model_common_instancing", defBasic)))); + + m->Model.ModShaderPlayer = LitRenderModifierPtr(new ShaderRenderModifier(CShaderTechnique(m->shaderManager.LoadProgram( + "model_common", defColored)))); + m->Model.ModShaderPlayerInstancing = LitRenderModifierPtr(new ShaderRenderModifier(CShaderTechnique(m->shaderManager.LoadProgram( + "model_common_instancing", defColored)))); + + m->Model.ModShaderTransparent = LitRenderModifierPtr(new ShaderRenderModifier( + techTransparent)); + m->Model.ModShaderTransparentShadow = LitRenderModifierPtr(new ShaderRenderModifier( + techTransparentShadow)); + + m->ShadersDirty = false; +} bool CRenderer::Open(int width, int height) { @@ -524,6 +652,7 @@ bool CRenderer::Open(int width, int height) m_VertexShader = 0; } + // model rendering m->Model.VertexFF[AmbientDiffuse] = ModelVertexRendererPtr(new FixedFunctionModelRenderer(false)); m->Model.VertexFF[OnlyDiffuse] = ModelVertexRendererPtr(new FixedFunctionModelRenderer(true)); @@ -538,6 +667,8 @@ bool CRenderer::Open(int width, int height) m->Model.VertexInstancing[OnlyDiffuse] = ModelVertexRendererPtr(new InstancingModelRenderer(true)); } m->Model.VertexPolygonSort = ModelVertexRendererPtr(new PolygonSortModelRenderer); + m->Model.VertexRendererShader = ModelVertexRendererPtr(new ShaderModelRenderer); + m->Model.VertexInstancingShader = ModelVertexRendererPtr(new ShaderInstancingModelRenderer); for(int vertexType = 0; vertexType < NumVertexTypes; ++vertexType) { @@ -559,6 +690,12 @@ bool CRenderer::Open(int width, int height) m->Model.pal_TranspSortAll = new SortModelRenderer(m->Model.VertexPolygonSort); + m->Model.pal_NormalShader = new BatchModelRenderer(m->Model.VertexRendererShader); + m->Model.pal_NormalInstancingShader = new BatchModelRenderer(m->Model.VertexInstancingShader); + m->Model.pal_PlayerShader = new BatchModelRenderer(m->Model.VertexRendererShader); + m->Model.pal_PlayerInstancingShader = new BatchModelRenderer(m->Model.VertexInstancingShader); + m->Model.pal_TranspShader = new SortModelRenderer(m->Model.VertexRendererShader); + m->Model.ModWireframe = RenderModifierPtr(new WireframeRenderModifier); m->Model.ModPlain = RenderModifierPtr(new PlainRenderModifier); m->Model.ModPlainLit = LitRenderModifierPtr(new PlainLitRenderModifier); @@ -628,6 +765,7 @@ void CRenderer::SetOptionBool(enum Option opt,bool value) break; case OPT_SHADOWS: m_Options.m_Shadows=value; + ReloadShaders(); break; case OPT_FANCYWATER: m_Options.m_FancyWater=value; @@ -688,8 +826,8 @@ void CRenderer::SetRenderPath(RenderPath rp) // Renderer has been opened, so validate the selected renderpath if (rp == RP_DEFAULT) { - if (m->CanUseRenderPathVertexShader()) - rp = RP_VERTEXSHADER; + if (m_Caps.m_ARBProgram) + rp = RP_SHADER; else rp = RP_FIXED; } @@ -703,7 +841,20 @@ void CRenderer::SetRenderPath(RenderPath rp) } } + if (rp == RP_SHADER) + { + if (!m_Caps.m_ARBProgram) + { + LOGWARNING(L"Falling back to fixed function\n"); + rp = RP_FIXED; + } + } + m_Options.m_RenderPath = rp; + + // We might need to regenerate some render data after changing path + if (g_Game) + g_Game->GetWorld()->GetTerrain()->MakeDirty(RENDERDATA_UPDATE_COLOR); } @@ -713,6 +864,7 @@ CStr CRenderer::GetRenderPathName(RenderPath rp) case RP_DEFAULT: return "default"; case RP_FIXED: return "fixed"; case RP_VERTEXSHADER: return "vertexshader"; + case RP_SHADER: return "shader"; default: return "(invalid)"; } } @@ -723,6 +875,8 @@ CRenderer::RenderPath CRenderer::GetRenderPathByName(const CStr& name) return RP_FIXED; if (name == "vertexshader") return RP_VERTEXSHADER; + if (name == "shader") + return RP_SHADER; if (name == "default") return RP_DEFAULT; @@ -758,12 +912,54 @@ void CRenderer::BeginFrame() { PROFILE("begin frame"); - if (m_VertexShader) - m_VertexShader->BeginFrame(); - // zero out all the per-frame stats m_Stats.Reset(); + // handle the new shader-based approach + if (m_Options.m_RenderPath == RP_SHADER) + { + if (m->ShadersDirty) + ReloadShaders(); + + m->Model.ModShaderNormal->SetShadowMap(m->shadow); + m->Model.ModShaderNormal->SetLightEnv(m_LightEnv); + + m->Model.ModShaderNormalInstancing->SetShadowMap(m->shadow); + m->Model.ModShaderNormalInstancing->SetLightEnv(m_LightEnv); + + m->Model.ModShaderPlayer->SetShadowMap(m->shadow); + m->Model.ModShaderPlayer->SetLightEnv(m_LightEnv); + + m->Model.ModShaderPlayerInstancing->SetShadowMap(m->shadow); + m->Model.ModShaderPlayerInstancing->SetLightEnv(m_LightEnv); + + m->Model.ModShaderTransparent->SetShadowMap(m->shadow); + m->Model.ModShaderTransparent->SetLightEnv(m_LightEnv); + + m->Model.ModNormal = m->Model.ModShaderNormal; + m->Model.ModNormalInstancing = m->Model.ModShaderNormalInstancing; + m->Model.ModPlayer = m->Model.ModShaderPlayer; + m->Model.ModPlayerInstancing = m->Model.ModShaderPlayerInstancing; + m->Model.ModSolid = m->Model.ModShaderSolidColor; + m->Model.ModSolidInstancing = m->Model.ModShaderSolidColorInstancing; + m->Model.ModSolidPlayer = m->Model.ModShaderSolidPlayerColor; + m->Model.ModSolidPlayerInstancing = m->Model.ModShaderSolidPlayerColorInstancing; + m->Model.ModTransparent = m->Model.ModShaderTransparent; + + m->Model.Normal = m->Model.pal_NormalShader; + m->Model.NormalInstancing = m->Model.pal_NormalInstancingShader; + + m->Model.Player = m->Model.pal_PlayerShader; + m->Model.PlayerInstancing = m->Model.pal_PlayerInstancingShader; + + m->Model.Transp = m->Model.pal_TranspShader; + + return; + } + + if (m_VertexShader) + m_VertexShader->BeginFrame(); + // choose model renderers for this frame int vertexType; @@ -771,10 +967,12 @@ void CRenderer::BeginFrame() { vertexType = OnlyDiffuse; m->Model.ModNormal = m->Model.ModPlainLit; + m->Model.ModNormalInstancing = m->Model.ModPlainLit; m->Model.ModPlainLit->SetShadowMap(m->shadow); m->Model.ModPlainLit->SetLightEnv(m_LightEnv); m->Model.ModPlayer = m->Model.ModPlayerLit; + m->Model.ModPlayerInstancing = m->Model.ModPlayerLit; m->Model.ModPlayerLit->SetShadowMap(m->shadow); m->Model.ModPlayerLit->SetLightEnv(m_LightEnv); @@ -786,7 +984,9 @@ void CRenderer::BeginFrame() { vertexType = AmbientDiffuse; m->Model.ModNormal = m->Model.ModPlain; + m->Model.ModNormalInstancing = m->Model.ModPlain; m->Model.ModPlayer = m->Model.ModPlayerUnlit; + m->Model.ModPlayerInstancing = m->Model.ModPlayerUnlit; m->Model.ModTransparent = m->Model.ModTransparentUnlit; } @@ -815,6 +1015,11 @@ void CRenderer::BeginFrame() m->Model.Player = m->Model.pal_PlayerFF[vertexType]; } + m->Model.ModSolid = m->Model.ModSolidColor; + m->Model.ModSolidInstancing = m->Model.ModSolidColor; + m->Model.ModSolidPlayer = m->Model.ModSolidPlayerColor; + m->Model.ModSolidPlayerInstancing = m->Model.ModSolidPlayerColor; + if (m_SortAllTransparent) m->Model.Transp = m->Model.pal_TranspSortAll; else if (m_Options.m_RenderPath == RP_VERTEXSHADER) @@ -835,7 +1040,7 @@ void CRenderer::SetClearColor(SColor4ub color) void CRenderer::RenderShadowMap() { - PROFILE( "render shadow map" ); + PROFILE("render shadow map"); m->shadow->BeginRender(); @@ -843,10 +1048,19 @@ void CRenderer::RenderShadowMap() glColor3f(shadowTransp, shadowTransp, shadowTransp); // Figure out transparent rendering strategy - RenderModifierPtr transparentShadows = m->Model.ModTransparentShadow; + RenderModifierPtr transparentShadows; + if (GetRenderPath() == RP_SHADER) + { + transparentShadows = m->Model.ModShaderTransparentShadow; + } + else + { + if (m->shadow->GetUseDepthTexture()) + transparentShadows = m->Model.ModTransparentDepthShadow; + else + transparentShadows = m->Model.ModTransparentShadow; + } - if (m->shadow->GetUseDepthTexture()) - transparentShadows = m->Model.ModTransparentDepthShadow; // Render all closed models (i.e. models where rendering back faces will produce // the correct result) @@ -865,7 +1079,8 @@ void CRenderer::RenderShadowMap() { PROFILE("render models"); - m->CallModelRenderers(m->Model.ModSolidColor, m->Model.ModSolidColor, MODELFLAG_CASTSHADOWS); + m->CallModelRenderers(m->Model.ModSolid, m->Model.ModSolidInstancing, + m->Model.ModSolid, m->Model.ModSolidInstancing, MODELFLAG_CASTSHADOWS); } { @@ -882,88 +1097,108 @@ void CRenderer::RenderShadowMap() void CRenderer::RenderPatches() { - PROFILE( "render patches" ); + PROFILE("render patches"); // switch on wireframe if we need it - if (m_TerrainRenderMode==WIREFRAME) { - glPolygonMode(GL_FRONT_AND_BACK,GL_LINE); + if (m_TerrainRenderMode == WIREFRAME) + { + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); } // render all the patches, including blend pass - m->terrainRenderer->RenderTerrain((m_Caps.m_Shadows && m_Options.m_Shadows) ? m->shadow : 0); + if (GetRenderPath() == RP_SHADER) + m->terrainRenderer->RenderTerrainShader((m_Caps.m_Shadows && m_Options.m_Shadows) ? m->shadow : 0); + else + m->terrainRenderer->RenderTerrain((m_Caps.m_Shadows && m_Options.m_Shadows) ? m->shadow : 0); - if (m_TerrainRenderMode==WIREFRAME) { + + if (m_TerrainRenderMode == WIREFRAME) + { // switch wireframe off again - glPolygonMode(GL_FRONT_AND_BACK,GL_FILL); - } else if (m_TerrainRenderMode==EDGED_FACES) { + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + } + else if (m_TerrainRenderMode == EDGED_FACES) + { // edged faces: need to make a second pass over the data: // first switch on wireframe glPolygonMode(GL_FRONT_AND_BACK,GL_LINE); // setup some renderstate .. - glDepthMask(0); - ogl_tex_bind(0, 0); - glColor4f(1,1,1,0.35f); + glDisable(GL_TEXTURE_2D); + glColor3f(0.5f, 0.5f, 1.0f); glLineWidth(2.0f); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); - // render tiles edges m->terrainRenderer->RenderPatches(); // set color for outline - glColor3f(0,0,1); + glColor3f(0, 0, 1); glLineWidth(4.0f); // render outline of each patch m->terrainRenderer->RenderOutlines(); // .. and restore the renderstates - glDisable(GL_BLEND); - glDepthMask(1); - - // restore fill mode, and we're done glLineWidth(1.0f); - glPolygonMode(GL_FRONT_AND_BACK,GL_FILL); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } } void CRenderer::RenderModels() { - PROFILE( "render models "); + PROFILE("render models"); - // switch on wireframe if we need it - if (m_ModelRenderMode==WIREFRAME) { - glPolygonMode(GL_FRONT_AND_BACK,GL_LINE); + if (m_ModelRenderMode == WIREFRAME) + { + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); } - m->CallModelRenderers(m->Model.ModNormal, m->Model.ModPlayer, 0); + m->CallModelRenderers(m->Model.ModNormal, m->Model.ModNormalInstancing, + m->Model.ModPlayer, m->Model.ModPlayerInstancing, 0); - if (m_ModelRenderMode==WIREFRAME) { - // switch wireframe off again - glPolygonMode(GL_FRONT_AND_BACK,GL_FILL); - } else if (m_ModelRenderMode==EDGED_FACES) { - m->CallModelRenderers(m->Model.ModWireframe, m->Model.ModWireframe, 0); + if (m_ModelRenderMode == WIREFRAME) + { + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + } + else if (m_ModelRenderMode == EDGED_FACES) + { + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + glDisable(GL_TEXTURE_2D); + glColor3f(1.0f, 1.0f, 0.0f); + + m->CallModelRenderers(m->Model.ModSolid, m->Model.ModSolidInstancing, + m->Model.ModSolid, m->Model.ModSolidInstancing, 0); + + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } } void CRenderer::RenderTransparentModels() { - PROFILE( "render transparent models "); + PROFILE("render transparent models"); // switch on wireframe if we need it - if (m_ModelRenderMode==WIREFRAME) { - glPolygonMode(GL_FRONT_AND_BACK,GL_LINE); + if (m_ModelRenderMode == WIREFRAME) + { + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); } m->Model.Transp->Render(m->Model.ModTransparent, 0); - if (m_ModelRenderMode==WIREFRAME) { + if (m_ModelRenderMode == WIREFRAME) + { // switch wireframe off again - glPolygonMode(GL_FRONT_AND_BACK,GL_FILL); - } else if (m_ModelRenderMode==EDGED_FACES) { - m->Model.Transp->Render(m->Model.ModWireframe, 0); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + } + else if (m_ModelRenderMode == EDGED_FACES) + { + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + glDisable(GL_TEXTURE_2D); + glColor3f(1.0f, 0.0f, 0.0f); + + m->Model.Transp->Render(m->Model.ModSolid, 0); + + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } } @@ -1229,13 +1464,24 @@ void CRenderer::RenderSilhouettes() { PROFILE("render model occluders"); - m->CallModelRenderers(m->Model.ModSolidColor, m->Model.ModSolidColor, MODELFLAG_SILHOUETTE_OCCLUDER); + m->CallModelRenderers(m->Model.ModSolid, m->Model.ModSolidInstancing, + m->Model.ModSolid, m->Model.ModSolidInstancing, MODELFLAG_SILHOUETTE_OCCLUDER); } { PROFILE("render transparent occluders"); - // Reuse the depth shadow modifier to get alpha-tested rendering - m->Model.Transp->Render(m->Model.ModTransparentDepthShadow, MODELFLAG_SILHOUETTE_OCCLUDER); + if (GetRenderPath() == RP_SHADER) + { + glEnable(GL_ALPHA_TEST); + glAlphaFunc(GL_GREATER, 0.4f); + m->Model.Transp->Render(m->Model.ModShaderSolidTex, MODELFLAG_SILHOUETTE_OCCLUDER); + glDisable(GL_ALPHA_TEST); + } + else + { + // Reuse the depth shadow modifier to get alpha-tested rendering + m->Model.Transp->Render(m->Model.ModTransparentDepthShadow, MODELFLAG_SILHOUETTE_OCCLUDER); + } } glDepthFunc(GL_GEQUAL); @@ -1269,7 +1515,8 @@ void CRenderer::RenderSilhouettes() { PROFILE("render models"); - m->CallModelRenderers(m->Model.ModSolidPlayerColor, m->Model.ModSolidPlayerColor, MODELFLAG_SILHOUETTE_DISPLAY); + m->CallModelRenderers(m->Model.ModSolidPlayer, m->Model.ModSolidPlayerInstancing, + m->Model.ModSolidPlayer, m->Model.ModSolidPlayerInstancing, MODELFLAG_SILHOUETTE_DISPLAY); // (This won't render transparent objects with SILHOUETTE_DISPLAY - will // we have any units that need that?) } @@ -1738,6 +1985,11 @@ LibError CRenderer::ReloadChangedFileCB(void* param, const VfsPath& path) return INFO::OK; } +void CRenderer::MakeShadersDirty() +{ + m->ShadersDirty = true; +} + /////////////////////////////////////////////////////////////////////////////////////////////////// // Scripting Interface @@ -1801,6 +2053,19 @@ void CRenderer::JSI_SetDepthTextureBits(JSContext* ctx, jsval newval) m->shadow->SetDepthTextureBits(depthTextureBits); } +jsval CRenderer::JSI_GetShadows(JSContext*) +{ + return ToJSVal(m_Options.m_Shadows); +} + +void CRenderer::JSI_SetShadows(JSContext* ctx, jsval newval) +{ + if (!ToPrimitive(ctx, newval, m_Options.m_Shadows)) + return; + + ReloadShaders(); +} + jsval CRenderer::JSI_GetShadowAlphaFix(JSContext*) { return ToJSVal(m_Options.m_ShadowAlphaFix); @@ -1836,6 +2101,7 @@ void CRenderer::ScriptingInit() AddProperty(L"shadowZBias", &CRenderer::m_ShadowZBias); AddProperty(L"shadowMapSize", &CRenderer::m_ShadowMapSize); AddProperty(L"disableCopyShadow", &CRenderer::m_DisableCopyShadow); + AddProperty(L"shadows", &CRenderer::JSI_GetShadows, &CRenderer::JSI_SetShadows); AddProperty(L"depthTextureBits", &CRenderer::JSI_GetDepthTextureBits, &CRenderer::JSI_SetDepthTextureBits); AddProperty(L"shadowAlphaFix", &CRenderer::JSI_GetShadowAlphaFix, &CRenderer::JSI_SetShadowAlphaFix); AddProperty(L"skipSubmit", &CRenderer::m_SkipSubmit); @@ -1849,3 +2115,8 @@ CTextureManager& CRenderer::GetTextureManager() { return m->textureManager; } + +CShaderManager& CRenderer::GetShaderManager() +{ + return m->shaderManager; +} diff --git a/source/renderer/Renderer.h b/source/renderer/Renderer.h index 847b0abfe4..7699b2c985 100644 --- a/source/renderer/Renderer.h +++ b/source/renderer/Renderer.h @@ -25,6 +25,7 @@ #include "graphics/Camera.h" #include "graphics/SColor.h" +#include "graphics/ShaderProgram.h" #include "lib/ogl.h" #include "lib/res/handle.h" #include "ps/Singleton.h" @@ -42,6 +43,7 @@ class RenderPathVertexShader; class WaterManager; class SkyManager; class CTextureManager; +class CShaderManager; // rendering modes enum ERenderMode { WIREFRAME, SOLID, EDGED_FACES }; @@ -60,24 +62,6 @@ enum ERenderMode { WIREFRAME, SOLID, EDGED_FACES }; #define STREAM_POSTOUV3 (1 << 10) #define STREAM_TEXGENTOUV1 (1 << 11) -////////////////////////////////////////////////////////////////////////////////////////// -// SVertex3D: simple 3D vertex declaration -struct SVertex3D -{ - float m_Position[3]; - float m_TexCoords[2]; - unsigned int m_Color; -}; - -////////////////////////////////////////////////////////////////////////////////////////// -// SVertex2D: simple 2D vertex declaration -struct SVertex2D -{ - float m_Position[2]; - float m_TexCoords[2]; - unsigned int m_Color; -}; - // access to sole renderer object #define g_Renderer CRenderer::GetSingleton() @@ -110,7 +94,10 @@ public: RP_FIXED, // Use (GL 2.0) vertex shaders for T&L when possible. - RP_VERTEXSHADER + RP_VERTEXSHADER, + + // Use new ARB/GLSL system + RP_SHADER }; // stats class - per frame counts of number of draw calls, poly counts etc @@ -147,10 +134,13 @@ public: float m_LodBias; RenderPath m_RenderPath; bool m_ShadowAlphaFix; + bool m_ARBProgramShadow; } m_Options; struct Caps { bool m_VBO; + bool m_ARBProgram; + bool m_ARBProgramShadow; bool m_VertexShader; bool m_FragmentShader; bool m_Shadows; @@ -193,6 +183,9 @@ public: // set color used to clear screen in BeginFrame() void SetClearColor(SColor4ub color); + // trigger a reload of shaders (when parameters they depend on have changed) + void MakeShadersDirty(); + /** * Set up the camera used for rendering the next scene; this includes * setting OpenGL state like viewport, projection and modelview matrices. @@ -277,6 +270,8 @@ public: CTextureManager& GetTextureManager(); + CShaderManager& GetShaderManager(); + /** * SetFastPlayerColor: Tell the renderer which path to take for * player colored models. Both paths should provide the same visual @@ -310,7 +305,9 @@ protected: friend class SortModelRenderer; friend class RenderPathVertexShader; friend class HWLightingModelRenderer; + friend class ShaderModelRenderer; friend class InstancingModelRenderer; + friend class ShaderInstancingModelRenderer; friend class TerrainRenderer; // scripting @@ -324,6 +321,8 @@ protected: void JSI_SetUseDepthTexture(JSContext* ctx, jsval newval); jsval JSI_GetDepthTextureBits(JSContext*); void JSI_SetDepthTextureBits(JSContext* ctx, jsval newval); + jsval JSI_GetShadows(JSContext*); + void JSI_SetShadows(JSContext* ctx, jsval newval); jsval JSI_GetShadowAlphaFix(JSContext*); void JSI_SetShadowAlphaFix(JSContext* ctx, jsval newval); jsval JSI_GetSky(JSContext*); @@ -362,6 +361,8 @@ protected: // enable oblique frustum clipping with the given clip plane void SetObliqueFrustumClipping(const CVector4D& clipPlane, int sign); + void ReloadShaders(); + // hotloading static LibError ReloadChangedFileCB(void* param, const VfsPath& path); diff --git a/source/renderer/TerrainRenderer.cpp b/source/renderer/TerrainRenderer.cpp index 3816313806..239d7349bb 100644 --- a/source/renderer/TerrainRenderer.cpp +++ b/source/renderer/TerrainRenderer.cpp @@ -30,6 +30,7 @@ #include "graphics/Terrain.h" #include "graphics/GameView.h" #include "graphics/Model.h" +#include "graphics/ShaderManager.h" #include "maths/MathUtil.h" @@ -225,6 +226,9 @@ void TerrainRenderer::RenderTerrain(ShadowMap* shadow) // no need to write to the depth buffer a second time glDepthMask(0); + // The decal color array contains lighting data, which we don't want in this non-shader mode + glDisableClientState(GL_COLOR_ARRAY); + // render blend passes for each patch PROFILE_START("render terrain blends"); CPatchRData::RenderBlends(m->visiblePatches); @@ -497,6 +501,134 @@ void TerrainRenderer::RenderTerrain(ShadowMap* shadow) } +/////////////////////////////////////////////////////////////////// + +/** + * Set up all the uniforms for a shader pass. + */ +void TerrainRenderer::PrepareShader(const CShaderProgramPtr& shader, ShadowMap* shadow) +{ + const CLightEnv& lightEnv = g_Renderer.GetLightEnv(); + + if (shadow) + { + shader->BindTexture("shadowTex", shadow->GetTexture()); + shader->Uniform("shadowTransform", shadow->GetTextureMatrix()); + } + + CLOSTexture& los = g_Game->GetView()->GetLOSTexture(); + shader->BindTexture("losTex", los.GetTexture()); + shader->Uniform("losTransform", los.GetTextureMatrix()[0], los.GetTextureMatrix()[12], 0.f, 0.f); + + shader->Uniform("ambient", lightEnv.m_TerrainAmbientColor); + shader->Uniform("sunColor", lightEnv.m_SunColor); +} + +void TerrainRenderer::RenderTerrainShader(ShadowMap* shadow) +{ + debug_assert(m->phase == Phase_Render); + + CShaderManager& shaderManager = g_Renderer.GetShaderManager(); + + typedef std::map Defines; + Defines defBasic; + if (shadow) + { + defBasic["USE_SHADOW"] = "1"; + if (g_Renderer.m_Caps.m_ARBProgramShadow && g_Renderer.m_Options.m_ARBProgramShadow) + defBasic["USE_FP_SHADOW"] = "1"; + } + + defBasic["LIGHTING_MODEL_" + g_Renderer.GetLightEnv().GetLightingModel()] = "1"; + + CShaderProgramPtr shaderBase(shaderManager.LoadProgram("terrain_base", defBasic)); + CShaderProgramPtr shaderBlend(shaderManager.LoadProgram("terrain_blend", defBasic)); + CShaderProgramPtr shaderDecal(shaderManager.LoadProgram("terrain_decal", defBasic)); + + // render the solid black sides of the map first + g_Renderer.BindTexture(0, 0); + glEnableClientState(GL_VERTEX_ARRAY); + glColor3f(0, 0, 0); + PROFILE_START("render terrain sides"); + for (size_t i = 0; i < m->visiblePatches.size(); ++i) + m->visiblePatches[i]->RenderSides(); + PROFILE_END("render terrain sides"); + + // switch on required client states + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glEnableClientState(GL_COLOR_ARRAY); // diffuse lighting colours + + shaderBase->Bind(); + PrepareShader(shaderBase, shadow); + + PROFILE_START("render terrain base"); + CPatchRData::RenderBases(m->visiblePatches); + PROFILE_END("render terrain base"); + + shaderBase->Unbind(); + + // render blends + + shaderBlend->Bind(); + PrepareShader(shaderBlend, shadow); + + // switch on the composite alpha map texture + (void)ogl_tex_bind(g_Renderer.m_hCompositeAlphaMap, 1); + + // switch on second uv set + pglClientActiveTextureARB(GL_TEXTURE1); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + + // switch on blending + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + // no need to write to the depth buffer a second time + glDepthMask(0); + + // render blend passes for each patch + PROFILE_START("render terrain blends"); + CPatchRData::RenderBlends(m->visiblePatches); + PROFILE_END("render terrain blends"); + + // Disable second texcoord array + pglClientActiveTextureARB(GL_TEXTURE1); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + + shaderBlend->Unbind(); + + // Render terrain decals + + shaderDecal->Bind(); + PrepareShader(shaderDecal, shadow); + + g_Renderer.BindTexture(1, 0); + pglActiveTextureARB(GL_TEXTURE0); + pglClientActiveTextureARB(GL_TEXTURE0); + + PROFILE_START("render terrain decals"); + for (size_t i = 0; i < m->visibleDecals.size(); ++i) + m->visibleDecals[i]->Render(); + PROFILE_END("render terrain decals"); + + shaderDecal->Unbind(); + + // restore OpenGL state + g_Renderer.BindTexture(1, 0); + g_Renderer.BindTexture(2, 0); + g_Renderer.BindTexture(3, 0); + + pglClientActiveTextureARB(GL_TEXTURE0); + pglActiveTextureARB(GL_TEXTURE0); + glDepthMask(1); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_BLEND); + glDisableClientState(GL_COLOR_ARRAY); + glDisableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); +} + + /////////////////////////////////////////////////////////////////// // Render un-textured patches as polygons void TerrainRenderer::RenderPatches() diff --git a/source/renderer/TerrainRenderer.h b/source/renderer/TerrainRenderer.h index 9db03087fe..6d6592dc3c 100644 --- a/source/renderer/TerrainRenderer.h +++ b/source/renderer/TerrainRenderer.h @@ -82,6 +82,12 @@ public: */ void RenderTerrain(ShadowMap* shadow); + /** + * Render textured terrain, as with RenderTerrain, but using shaders + * instead of multitexturing. + */ + void RenderTerrainShader(ShadowMap* shadow); + /** * RenderPatches: Render all patches un-textured as polygons. * @@ -114,6 +120,8 @@ public: private: TerrainRendererInternals* m; + + void PrepareShader(const CShaderProgramPtr& shader, ShadowMap* shadow); }; #endif // INCLUDED_TERRAINRENDERER diff --git a/source/simulation2/components/ICmpPlayerManager.cpp b/source/simulation2/components/ICmpPlayerManager.cpp index f13156e481..944cbe8ebc 100644 --- a/source/simulation2/components/ICmpPlayerManager.cpp +++ b/source/simulation2/components/ICmpPlayerManager.cpp @@ -35,6 +35,11 @@ public: m_Script.CallVoid("AddPlayer", (int)ent); } + virtual int32_t GetNumPlayers() + { + return m_Script.Call("GetNumPlayers"); + } + virtual entity_id_t GetPlayerByID(int32_t id) { return m_Script.Call("GetPlayerByID", (int)id); diff --git a/source/simulation2/components/ICmpPlayerManager.h b/source/simulation2/components/ICmpPlayerManager.h index 7fa778ac1f..207ad8a68e 100644 --- a/source/simulation2/components/ICmpPlayerManager.h +++ b/source/simulation2/components/ICmpPlayerManager.h @@ -28,6 +28,8 @@ class ICmpPlayerManager : public IComponent public: virtual void AddPlayer(entity_id_t ent) = 0; + virtual int32_t GetNumPlayers() = 0; + virtual entity_id_t GetPlayerByID(int32_t id) = 0; DECLARE_INTERFACE_TYPE(PlayerManager) diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.cpp b/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.cpp index 97c2d0cac0..0b27cf88ed 100644 --- a/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.cpp +++ b/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.cpp @@ -311,6 +311,9 @@ enum ID_Screenshot, ID_JavaScript, ID_CameraReset, + ID_RenderPathFixed, + ID_RenderPathVertexShader, + ID_RenderPathShader, ID_Toolbar // must be last in the list }; @@ -334,6 +337,9 @@ BEGIN_EVENT_TABLE(ScenarioEditor, wxFrame) EVT_MENU(ID_Screenshot, ScenarioEditor::OnScreenshot) EVT_MENU(ID_JavaScript, ScenarioEditor::OnJavaScript) EVT_MENU(ID_CameraReset, ScenarioEditor::OnCameraReset) + EVT_MENU(ID_RenderPathFixed, ScenarioEditor::OnRenderPath) + EVT_MENU(ID_RenderPathVertexShader, ScenarioEditor::OnRenderPath) + EVT_MENU(ID_RenderPathShader, ScenarioEditor::OnRenderPath) EVT_IDLE(ScenarioEditor::OnIdle) END_EVENT_TABLE() @@ -480,6 +486,12 @@ ScenarioEditor::ScenarioEditor(wxWindow* parent, ScriptInterface& scriptInterfac menuMisc->Append(ID_Screenshot, _("&Screenshot")); menuMisc->Append(ID_JavaScript, _("&JS console")); menuMisc->Append(ID_CameraReset, _("&Reset camera")); + + wxMenu *menuRP = new wxMenu; + menuMisc->AppendSubMenu(menuRP, _("Render &path")); + menuRP->Append(ID_RenderPathFixed, _("&Fixed function")); + menuRP->Append(ID_RenderPathVertexShader, _("&Vertex shader (old)")); + menuRP->Append(ID_RenderPathShader, _("&Shader (new)")); } m_FileHistory.Load(*wxConfigBase::Get()); @@ -771,6 +783,22 @@ void ScenarioEditor::OnCameraReset(wxCommandEvent& WXUNUSED(event)) POST_MESSAGE(CameraReset, ()); } +void ScenarioEditor::OnRenderPath(wxCommandEvent& event) +{ + switch (event.GetId()) + { + case ID_RenderPathFixed: + POST_MESSAGE(SetViewParamS, (eRenderView::GAME, L"renderpath", L"fixed")); + break; + case ID_RenderPathVertexShader: + POST_MESSAGE(SetViewParamS, (eRenderView::GAME, L"renderpath", L"vertexshader")); + break; + case ID_RenderPathShader: + POST_MESSAGE(SetViewParamS, (eRenderView::GAME, L"renderpath", L"shader")); + break; + } +} + ////////////////////////////////////////////////////////////////////////// Position::Position(const wxPoint& pt) diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.h b/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.h index 21a9aefc76..42950f5c1b 100644 --- a/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.h +++ b/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.h @@ -51,6 +51,7 @@ public: void OnMediaPlayer(wxCommandEvent& event); void OnJavaScript(wxCommandEvent& event); void OnCameraReset(wxCommandEvent& event); + void OnRenderPath(wxCommandEvent& event); void OpenFile(const wxString& name); diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Environment/Environment.cpp b/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Environment/Environment.cpp index 22c3750c89..a975fd6df3 100644 --- a/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Environment/Environment.cpp +++ b/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Environment/Environment.cpp @@ -227,6 +227,8 @@ EnvironmentSidebar::EnvironmentSidebar(ScenarioEditor& scenarioEditor, wxWindow* sunSizer->Add(new VariableSliderBox(this, _("Sun elevation"), g_EnvironmentSettings.sunelevation, -M_PIf/2, M_PIf/2), wxSizerFlags().Expand()); sunSizer->Add(new VariableSliderBox(this, _("Sun overbrightness"), g_EnvironmentSettings.sunoverbrightness, 1.0f, 3.0f), wxSizerFlags().Expand()); + sunSizer->Add(m_LightingModelList = new VariableListBox(this, _("Light model"), g_EnvironmentSettings.lightingmodel), wxSizerFlags().Expand()); + m_MainSizer->Add(new LightControl(this, wxSize(150, 150), g_EnvironmentSettings)); m_MainSizer->Add(m_SkyList = new VariableListBox(this, _("Sky set"), g_EnvironmentSettings.skyset)); m_MainSizer->Add(new VariableColourBox(this, _("Sun colour"), g_EnvironmentSettings.suncolour)); @@ -248,6 +250,13 @@ void EnvironmentSidebar::OnFirstDisplay() qry_env.Post(); g_EnvironmentSettings = qry_env.settings; + + std::vector lightingModels; + lightingModels.push_back(L"old"); + lightingModels.push_back(L"standard"); + m_LightingModelList->SetChoices(lightingModels); + + g_EnvironmentSettings.NotifyObservers(); // TODO: reupdate everything when loading a new map... } diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Environment/Environment.h b/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Environment/Environment.h index 738214e60a..9aed4a9e1a 100644 --- a/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Environment/Environment.h +++ b/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Environment/Environment.h @@ -30,6 +30,7 @@ protected: virtual void OnFirstDisplay(); private: + VariableListBox* m_LightingModelList; VariableListBox* m_SkyList; ObservableScopedConnection m_Conn; }; diff --git a/source/tools/atlas/GameInterface/Handlers/EnvironmentHandlers.cpp b/source/tools/atlas/GameInterface/Handlers/EnvironmentHandlers.cpp index 1bc5b973fd..496426b113 100644 --- a/source/tools/atlas/GameInterface/Handlers/EnvironmentHandlers.cpp +++ b/source/tools/atlas/GameInterface/Handlers/EnvironmentHandlers.cpp @@ -62,6 +62,8 @@ sEnvironmentSettings GetSettings() s.sunrotation = sunrotation; s.sunelevation = g_LightEnv.GetElevation(); + s.lightingmodel = CStr(g_LightEnv.GetLightingModel()).FromUTF8(); + s.skyset = g_Renderer.GetSkyManager()->GetSkySet(); // RGBColor (CVector3D) colours @@ -103,6 +105,8 @@ void SetSettings(const sEnvironmentSettings& s) g_LightEnv.SetRotation(s.sunrotation); g_LightEnv.SetElevation(s.sunelevation); + g_LightEnv.SetLightingModel(CStrW(*s.lightingmodel).ToUTF8()); + CStrW skySet = *s.skyset; if (skySet.length() == 0) skySet = L"default"; diff --git a/source/tools/atlas/GameInterface/Messages.h b/source/tools/atlas/GameInterface/Messages.h index fbf2c6f81d..4d293bc697 100644 --- a/source/tools/atlas/GameInterface/Messages.h +++ b/source/tools/atlas/GameInterface/Messages.h @@ -345,6 +345,10 @@ struct sEnvironmentSettings // (struct Colour stores as normal u8, 0..255) Shareable sunoverbrightness; // range 1..3 + // support different lighting models ("old" for the version compatible with old scenarios, + // "standard" for the new normal model that supports much brighter lighting) + Shareable lightingmodel; + Shareable skyset; Shareable suncolour; diff --git a/source/tools/atlas/GameInterface/View.cpp b/source/tools/atlas/GameInterface/View.cpp index 7508312ddc..a033e686ec 100644 --- a/source/tools/atlas/GameInterface/View.cpp +++ b/source/tools/atlas/GameInterface/View.cpp @@ -260,6 +260,10 @@ void ViewGame::SetParam(const std::wstring& name, const std::wstring& value) if (!cmpPathfinder.null()) cmpPathfinder->SetDebugOverlay(!value.empty()); } + else if (name == L"renderpath") + { + g_Renderer.SetRenderPath(g_Renderer.GetRenderPathByName(CStrW(value).ToUTF8())); + } } CCamera& ViewGame::GetCamera()