summaryrefslogtreecommitdiff
path: root/Runtime/GfxDevice/opengles30
diff options
context:
space:
mode:
Diffstat (limited to 'Runtime/GfxDevice/opengles30')
-rw-r--r--Runtime/GfxDevice/opengles30/AssertGLES30.cpp58
-rw-r--r--Runtime/GfxDevice/opengles30/AssertGLES30.h40
-rw-r--r--Runtime/GfxDevice/opengles30/CombinerGLES30.cpp21
-rw-r--r--Runtime/GfxDevice/opengles30/CombinerGLES30.h10
-rw-r--r--Runtime/GfxDevice/opengles30/ConstantBuffersGLES30.cpp338
-rw-r--r--Runtime/GfxDevice/opengles30/ConstantBuffersGLES30.h72
-rw-r--r--Runtime/GfxDevice/opengles30/ContextGLES30.cpp288
-rw-r--r--Runtime/GfxDevice/opengles30/ContextGLES30.h37
-rw-r--r--Runtime/GfxDevice/opengles30/DataBuffersGLES30.cpp429
-rw-r--r--Runtime/GfxDevice/opengles30/DataBuffersGLES30.h137
-rw-r--r--Runtime/GfxDevice/opengles30/DebugGLES30.cpp38
-rw-r--r--Runtime/GfxDevice/opengles30/DebugGLES30.h139
-rw-r--r--Runtime/GfxDevice/opengles30/FixedFunctionStateGLES30.cpp71
-rw-r--r--Runtime/GfxDevice/opengles30/FixedFunctionStateGLES30.h65
-rw-r--r--Runtime/GfxDevice/opengles30/GfxDeviceGLES30.cpp3328
-rw-r--r--Runtime/GfxDevice/opengles30/GfxDeviceGLES30.h191
-rw-r--r--Runtime/GfxDevice/opengles30/GpuProgramsGLES30.cpp1176
-rw-r--r--Runtime/GfxDevice/opengles30/GpuProgramsGLES30.h88
-rw-r--r--Runtime/GfxDevice/opengles30/GpuProgramsGLES30_UniformCache.cpp62
-rw-r--r--Runtime/GfxDevice/opengles30/GpuProgramsGLES30_UniformCache.h50
-rw-r--r--Runtime/GfxDevice/opengles30/GpuPropertiesGLES30.cpp70
-rw-r--r--Runtime/GfxDevice/opengles30/GpuPropertiesGLES30.h3
-rw-r--r--Runtime/GfxDevice/opengles30/IncludesGLES30.h1
-rw-r--r--Runtime/GfxDevice/opengles30/RenderTextureGLES30.cpp551
-rw-r--r--Runtime/GfxDevice/opengles30/RenderTextureGLES30.h267
-rw-r--r--Runtime/GfxDevice/opengles30/ShaderGeneratorGLES30.cpp824
-rw-r--r--Runtime/GfxDevice/opengles30/ShaderGeneratorGLES30.h24
-rw-r--r--Runtime/GfxDevice/opengles30/TextureIdMapGLES30.h17
-rw-r--r--Runtime/GfxDevice/opengles30/TexturesGLES30.cpp535
-rw-r--r--Runtime/GfxDevice/opengles30/TexturesGLES30.h22
-rw-r--r--Runtime/GfxDevice/opengles30/TimerQueryGLES30.cpp100
-rw-r--r--Runtime/GfxDevice/opengles30/TimerQueryGLES30.h47
-rw-r--r--Runtime/GfxDevice/opengles30/TransformFeedbackSkinnedMesh.cpp762
-rw-r--r--Runtime/GfxDevice/opengles30/TransformFeedbackSkinnedMesh.h47
-rw-r--r--Runtime/GfxDevice/opengles30/UnityGLES30Ext.cpp14
-rw-r--r--Runtime/GfxDevice/opengles30/UnityGLES30Ext.h276
-rw-r--r--Runtime/GfxDevice/opengles30/UtilsGLES30.cpp181
-rw-r--r--Runtime/GfxDevice/opengles30/UtilsGLES30.h37
-rw-r--r--Runtime/GfxDevice/opengles30/VBOGLES30.cpp1351
-rw-r--r--Runtime/GfxDevice/opengles30/VBOGLES30.h262
40 files changed, 12029 insertions, 0 deletions
diff --git a/Runtime/GfxDevice/opengles30/AssertGLES30.cpp b/Runtime/GfxDevice/opengles30/AssertGLES30.cpp
new file mode 100644
index 0000000..9f20a92
--- /dev/null
+++ b/Runtime/GfxDevice/opengles30/AssertGLES30.cpp
@@ -0,0 +1,58 @@
+#include "UnityPrefix.h"
+#include "AssertGLES30.h"
+#include "IncludesGLES30.h"
+
+#if GFX_SUPPORTS_OPENGLES30
+
+using namespace std;
+
+static const char* GetErrorString(GLenum glerr)
+{
+ // Error descriptions taken from OpenGLES 1.1 specification (page 13)
+ switch(glerr)
+ {
+ case GL_NO_ERROR:
+ return "GL_NO_ERROR: No error occured";
+ case GL_INVALID_ENUM:
+ return "GL_INVALID_ENUM: enum argument out of range";
+ case GL_INVALID_VALUE:
+ return "GL_INVALID_VALUE: Numeric argument out of range";
+ case GL_INVALID_OPERATION:
+ return "GL_INVALID_OPERATION: Operation illegal in current state";
+ case GL_OUT_OF_MEMORY:
+ return "GL_OUT_OF_MEMORY: Not enough memory left to execute command";
+ case GL_INVALID_FRAMEBUFFER_OPERATION:
+ return "GL_INVALID_FRAMEBUFFER_OPERATION: Framebuffer is not complete or incompatible with command";
+ default:
+#if UNTIY_WEBGL
+ printf_console("AssertGles20::GetErrorString invoked for unknown error %d",glerr);
+#endif
+ return "Unknown error";
+ }
+}
+
+void CheckOpenGLES3Error (const char *prefix, const char* file, long line)
+{
+ const int kMaxErrors = 10;
+ int counter = 0;
+
+ GLenum glerr;
+ while( (glerr = glGetError ()) != GL_NO_ERROR )
+ {
+ string errorString(GetErrorString(glerr));
+
+ if (prefix)
+ errorString = string(prefix) + ": " + errorString;
+
+ DebugStringToFile (errorString.c_str(), 0, file, line, kAssert);
+
+ ++counter;
+ if( counter > kMaxErrors )
+ {
+ printf_console( "GLES: error count exceeds %i, stop reporting errors\n", kMaxErrors );
+ return;
+ }
+ }
+}
+
+#endif // GFX_SUPPORTS_OPENGLES30
diff --git a/Runtime/GfxDevice/opengles30/AssertGLES30.h b/Runtime/GfxDevice/opengles30/AssertGLES30.h
new file mode 100644
index 0000000..bb9c26d
--- /dev/null
+++ b/Runtime/GfxDevice/opengles30/AssertGLES30.h
@@ -0,0 +1,40 @@
+#ifndef GLES_ASSERTGLES30_H
+#define GLES_ASSERTGLES30_H
+
+#include "Runtime/Utilities/LogAssert.h"
+
+void CheckOpenGLES3Error (const char *prefix, const char* file, long line);
+
+#if !UNITY_RELEASE || UNITY_WEBGL
+ #ifndef GLESAssert
+ /// Asserts for checking the OpenGL error state
+ #define GLESAssert() { CheckOpenGLES3Error (NULL, __FILE__, __LINE__); }
+ #endif
+ #define GLESAssertString(x) { CheckOpenGLES3Error (x, __FILE__, __LINE__); }
+
+ #define GLES30_PRINT_GL_TRACE 0
+ #if GLES30_PRINT_GL_TRACE
+ #define GLESCHKSTRINGIFY(x) GLESCHKSTRINGIFY2(x)
+ #define GLESCHKSTRINGIFY2(x) #x
+
+ #define GLES_CHK(x) do { {printf_console("GLES: %s %s %d\n", GLESCHKSTRINGIFY(x), __FILE__, __LINE__); x;} GLESAssert(); } while(0)
+ #else
+ #define GLES_CHK(x) do { {x;} GLESAssert(); } while(0)
+ #endif
+#else
+
+ #ifndef GLESAssert
+ #define GLESAssert()
+ #endif
+ #define GLESAssertString(x)
+ #define GLES_CHK(x) x
+
+
+#endif
+
+
+//#define GLES_CHK(x) do {} while(0)
+//#define GLES_CHK(x)
+//#define GLES_CHK(x) do { printf_console("GLES: %s %d\n", __FILE__, __LINE__); } while(0)
+
+#endif
diff --git a/Runtime/GfxDevice/opengles30/CombinerGLES30.cpp b/Runtime/GfxDevice/opengles30/CombinerGLES30.cpp
new file mode 100644
index 0000000..51f64c4
--- /dev/null
+++ b/Runtime/GfxDevice/opengles30/CombinerGLES30.cpp
@@ -0,0 +1,21 @@
+#include "UnityPrefix.h"
+#include "CombinerGLES30.h"
+#include "Runtime/Shaders/GraphicsCaps.h"
+#include "VBOGLES30.h"
+
+#if GFX_SUPPORTS_OPENGLES30
+
+TextureCombinersGLES3* TextureCombinersGLES3::Create (int count, const ShaderLab::TextureBinding* texEnvs)
+{
+ // check if we have enough vertex attributes to emulate this combiner
+ if (count + kGLES3AttribLocationTexCoord0 >= gGraphicsCaps.gles30.maxAttributes)
+ return NULL;
+
+ // create struct that holds texture combiner info object
+ TextureCombinersGLES3* combiners = new TextureCombinersGLES3();
+ combiners->count = count;
+ combiners->texEnvs = texEnvs;
+ return combiners;
+}
+
+#endif // GFX_SUPPORTS_OPENGLES30
diff --git a/Runtime/GfxDevice/opengles30/CombinerGLES30.h b/Runtime/GfxDevice/opengles30/CombinerGLES30.h
new file mode 100644
index 0000000..4073e99
--- /dev/null
+++ b/Runtime/GfxDevice/opengles30/CombinerGLES30.h
@@ -0,0 +1,10 @@
+#pragma once
+
+namespace ShaderLab { struct TextureBinding; }
+
+struct TextureCombinersGLES3
+{
+ static TextureCombinersGLES3* Create (int count, const ShaderLab::TextureBinding* texEnvs);
+ int count;
+ const ShaderLab::TextureBinding* texEnvs;
+};
diff --git a/Runtime/GfxDevice/opengles30/ConstantBuffersGLES30.cpp b/Runtime/GfxDevice/opengles30/ConstantBuffersGLES30.cpp
new file mode 100644
index 0000000..4a64c08
--- /dev/null
+++ b/Runtime/GfxDevice/opengles30/ConstantBuffersGLES30.cpp
@@ -0,0 +1,338 @@
+#include "UnityPrefix.h"
+#include "ConstantBuffersGLES30.h"
+#include "AssertGLES30.h"
+
+// NEVER enable this for release! Turns off all CB caching and makes things
+// very slow, for debugging.
+#define DEBUG_DISABLE_CONSTANT_BUFFER_CACHES (0 && !UNITY_RELEASE)
+
+#define CONSTANT_BUFFER_ID_MASK 0xB0000000
+#define MAKE_CONSTANT_BUFFER_ID(id) (id|CONSTANT_BUFFER_ID_MASK)
+
+
+#if DEBUG_GLES30_UNIFORM_BUFFER_STATS
+#include "External/shaderlab/Library/FastPropertyName.h"
+extern std::string g_LastParsedShaderName;
+#endif
+
+ConstantBuffersGLES30::ConstantBuffersGLES30()
+{
+ memset (m_ActiveBuffers, 0, sizeof(m_ActiveBuffers));
+}
+
+void ConstantBuffersGLES30::Clear()
+{
+ memset (m_ActiveBuffers, 0, sizeof(m_ActiveBuffers));
+ for (size_t i = 0; i < m_Buffers.size(); ++i)
+ {
+ ConstBuffer& cb = m_Buffers[i];
+ delete[] cb.data;
+ if (cb.buffer)
+ {
+ REGISTER_EXTERNAL_GFX_DEALLOCATION(MAKE_CONSTANT_BUFFER_ID(cb.buffer));
+ GLES_CHK(glDeleteBuffers(1, (GLuint*)&cb.buffer));
+ }
+ #if DEBUG_GLES30_CONSTANT_BUFFER_STATS
+ delete[] cb.changeCounts;
+ delete[] cb.tryCounts;
+ #endif
+ }
+ m_Buffers.clear();
+ m_BufferKeys.clear();
+}
+
+void ConstantBuffersGLES30::SetCBInfo(int id, int size)
+{
+ size_t n = m_Buffers.size();
+ Assert (m_BufferKeys.size() == n);
+ UInt32 key = id | (size<<16);
+ for (size_t i = 0; i < n; ++i)
+ {
+ if (m_BufferKeys[i] == key)
+ return;
+ }
+
+ // not found, create one
+ ConstBuffer cb;
+ cb.data = new UInt8[size];
+ memset (cb.data, 0, size);
+ cb.dirty = true;
+ cb.bindIndex = -1;
+ #if DEBUG_GLES30_CONSTANT_BUFFER_STATS
+ cb.statsDirty = 0;
+ cb.stats = 0;
+ ShaderLab::FastPropertyName name;
+ name.index = id;
+ printf_console ("GLES30 Uniform Buffer Info: new %s size=%i shader=%s\n", name.GetName(), size, g_LastParsedShaderName.c_str());
+ cb.changeCounts = new int[size/4];
+ memset (cb.changeCounts, 0, size);
+ cb.tryCounts = new int[size/4];
+ memset (cb.tryCounts, 0, size);
+ #endif
+
+ GLES_CHK(glGenBuffers(1, (GLuint*)&cb.buffer));
+ GLES_CHK(glBindBuffer(GL_UNIFORM_BUFFER, cb.buffer));
+ GLES_CHK(glBufferData(GL_UNIFORM_BUFFER, size, NULL, GL_DYNAMIC_DRAW));
+ REGISTER_EXTERNAL_GFX_ALLOCATION_REF(MAKE_CONSTANT_BUFFER_ID(cb.buffer),size,this);
+
+ m_Buffers.push_back (cb);
+ m_BufferKeys.push_back (key);
+}
+
+int ConstantBuffersGLES30::FindAndBindCB (int id, int bind, int size)
+{
+ UInt32 key = id | (size<<16);
+ int idx = 0;
+ for (ConstBufferKeys::const_iterator it = m_BufferKeys.begin(), itEnd = m_BufferKeys.end(); it != itEnd; ++it, ++idx)
+ {
+ if (*it == key)
+ {
+ ConstBuffer& cb = m_Buffers[idx];
+ if (bind >= 0)
+ {
+ cb.bindIndex = bind;
+ }
+ return idx;
+ }
+ }
+ Assert (false);
+ return -1;
+}
+
+void ConstantBuffersGLES30::ResetBinds()
+{
+ for (ConstBuffers::iterator it = m_Buffers.begin(), itEnd = m_Buffers.end(); it != itEnd; ++it)
+ {
+ it->bindIndex = -1;
+ }
+}
+
+void ConstantBuffersGLES30::SetCBConstant (int idx, int offset, const void* data, int size)
+{
+ Assert (idx >= 0 && idx < m_Buffers.size());
+ ConstBuffer& cb = m_Buffers[idx];
+ Assert (offset >= 0 && offset+size <= (m_BufferKeys[idx]>>16) && size > 0);
+
+ #if DEBUG_GLES30_CONSTANT_BUFFER_STATS
+ for (int i = offset/4; i < offset/4+size/4; ++i)
+ ++cb.tryCounts[i];
+ #endif
+
+ if (size == 4)
+ {
+ UInt32* dstData = (UInt32*)(cb.data+offset);
+ UInt32 srcData = *(UInt32*)data;
+ if (DEBUG_DISABLE_CONSTANT_BUFFER_CACHES || *dstData != srcData)
+ {
+ *dstData = srcData;
+ cb.dirty = true;
+
+ #if DEBUG_GLES30_CONSTANT_BUFFER_STATS
+ for (int i = offset/4; i < offset/4+size/4; ++i)
+ ++cb.changeCounts[i];
+ #endif
+ }
+ }
+ else
+ {
+ if (DEBUG_DISABLE_CONSTANT_BUFFER_CACHES || memcmp(cb.data+offset, data, size) != 0)
+ {
+ memcpy (cb.data+offset, data, size);
+ cb.dirty = true;
+
+ #if DEBUG_GLES30_CONSTANT_BUFFER_STATS
+ for (int i = offset/4; i < offset/4+size/4; ++i)
+ ++cb.changeCounts[i];
+ #endif
+ }
+ }
+}
+
+void ConstantBuffersGLES30::UpdateBuffers ()
+{
+ size_t n = m_Buffers.size();
+
+ #if !UNITY_RELEASE
+ // check if we have duplicate buffers bound to the same slot (should never happen!)
+ UInt32 bound = 0;
+ for (size_t i = 0; i < n; ++i)
+ {
+ ConstBuffer& cb = m_Buffers[i];
+ int bind = cb.bindIndex;
+ if (bind >= 0 && bind < 32)
+ {
+ Assert (!(bound & (1<<bind)));
+ bound |= (1<<bind);
+ }
+ }
+ #endif
+
+
+ for (size_t i = 0; i < n; ++i)
+ {
+ ConstBuffer& cb = m_Buffers[i];
+ if (DEBUG_DISABLE_CONSTANT_BUFFER_CACHES || cb.dirty)
+ {
+ GLES_CHK(glBindBuffer(GL_UNIFORM_BUFFER, cb.buffer));
+
+ UInt32 bufferSize = (m_BufferKeys[i]>>16);
+ GLES_CHK(glBufferData(GL_UNIFORM_BUFFER, bufferSize, cb.data, GL_DYNAMIC_DRAW));
+ //void* data = glMapBufferRange(GL_UNIFORM_BUFFER, 0, bufferSize, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
+ //GLESAssert();
+ //if (data != NULL)
+ //{
+ // ::memcpy(data, cb.data, bufferSize);
+ //}
+ //GLES_CHK(glUnmapBuffer(GL_UNIFORM_BUFFER));
+ #if DEBUG_GLES30_CONSTANT_BUFFER_STATS
+ ++cb.statsDirty;
+ #endif
+ }
+
+ // Bind
+ int bindIndex = cb.bindIndex;
+ if (bindIndex >= 0 && (DEBUG_DISABLE_CONSTANT_BUFFER_CACHES || m_ActiveBuffers[bindIndex] != cb.buffer))
+ {
+ GLES_CHK(glBindBufferBase(GL_UNIFORM_BUFFER, bindIndex, cb.buffer));
+ }
+ cb.dirty = false;
+ }
+}
+
+#if 0
+
+void ConstantBuffersD3D11::SetBuiltinCBConstant (int id, int offset, const void* data, int size)
+{
+ int idx = GetCBIndexByID (id);
+ ConstBuffer& cb = m_Buffers[idx];
+ Assert (offset >= 0 && offset+size <= (m_BufferKeys[idx]>>16) && size > 0);
+
+ #if DEBUG_D3D11_CONSTANT_BUFFER_STATS
+ for (int i = offset/4; i < offset/4+size/4; ++i)
+ ++cb.tryCounts[i];
+ #endif
+
+ if (DEBUG_DISABLE_CONSTANT_BUFFER_CACHES || memcmp(cb.data+offset, data, size) != 0)
+ {
+ memcpy (cb.data+offset, data, size);
+ cb.dirty = true;
+
+ #if DEBUG_D3D11_CONSTANT_BUFFER_STATS
+ for (int i = offset/4; i < offset/4+size/4; ++i)
+ ++cb.changeCounts[i];
+ #endif
+ }
+}
+
+#if DEBUG_D3D11_CONSTANT_BUFFER_STATS
+
+static void WriteTGAFile (const char* filename, int width, int height, const UInt8* bgr)
+{
+ FILE* f = fopen(filename, "wb");
+ // header
+ putc(0,f);
+ putc(0,f);
+ putc(2,f); // uncompressed RGB
+ putc(0,f); putc(0,f);
+ putc(0,f); putc(0,f);
+ putc(0,f);
+ putc(0,f); putc(0,f);
+ putc(0,f); putc(0,f);
+ putc((width & 0x00FF),f);
+ putc((width & 0xFF00)>>8,f);
+ putc((height & 0x00FF),f);
+ putc((height & 0xFF00)>>8,f);
+ putc(24,f); // 24 bit
+ putc(0x20,f); // vertical flip
+ // data
+ fwrite (bgr, 3, width*height, f);
+ fclose (f);
+}
+
+static void DensityToBGR (int density, UInt8* bgr)
+{
+ if (density < 1)
+ {
+ bgr[0] = bgr[1] = bgr[2] = 0;
+ return;
+ }
+ bgr[0] = clamp(40+density/4, 0, 255);
+ bgr[1] = clamp(40+density/4, 0, 255);
+ bgr[2] = clamp(40+density/4, 0, 255);
+}
+
+static void PutBGRPixelBlock (UInt8* img, int imgWidth, int x, int y, const UInt8* bgr)
+{
+ for (int i = 0; i < 4; ++i)
+ {
+ UInt8* ptr = img + ((y+i)*imgWidth+x) * 3;
+ for (int j = 0; j < 4; ++j, ptr += 3)
+ {
+ ptr[0] = bgr[0];
+ ptr[1] = bgr[1];
+ ptr[2] = bgr[2];
+ }
+ }
+}
+
+
+void ConstantBuffersD3D11::NewFrame()
+{
+ if (GetAsyncKeyState(VK_F7))
+ {
+ printf_console ("DX11 Constant Buffer stats:\n");
+ float traffic = 0.0f;
+ int uploads = 0;
+ int maxSize = 0;
+ for (size_t i = 0; i < m_BufferKeys.size(); ++i)
+ maxSize = std::max(int(m_BufferKeys[i]>>16), maxSize);
+
+ int imgWidth = maxSize+1;
+ int imgHeight = m_Buffers.size()*3*4;
+ UInt8* imgData = new UInt8[imgWidth*imgHeight*3];
+ memset (imgData, 0, imgWidth*imgHeight*3);
+
+ for (size_t i = 0; i < m_Buffers.size(); ++i)
+ {
+ ConstBuffer& cb = m_Buffers[i];
+ int cbId = (m_BufferKeys[i]&0xFFFF);
+ int cbSize = (m_BufferKeys[i]>>16);
+ ShaderLab::FastPropertyName name;
+ name.index = cbId;
+ traffic += (cbSize*cb.statsDirty)/1024.0f;
+ uploads += cb.statsDirty;
+ printf_console (" %s size:%i (%.1fkB in %i upl) vs:%i ps:%i\n", name.GetName(), cbSize, (cbSize*cb.statsDirty)/1024.0f, cb.statsDirty, cb.statsVS, cb.statsPS);
+ if (cb.statsDirty > 0)
+ {
+ for (int j = 0; j < cbSize/4; ++j)
+ {
+ UInt8 bgr[3];
+ DensityToBGR (cb.tryCounts[j], bgr);
+ PutBGRPixelBlock (imgData, imgWidth, j*4, i*3*4, bgr);
+ DensityToBGR (cb.changeCounts[j], bgr);
+ PutBGRPixelBlock (imgData, imgWidth, j*4, i*3*4+4, bgr);
+ }
+ }
+ for (int j = 0; j < 8; ++j)
+ {
+ imgData[((i*3*4+j)*imgWidth + cbSize)*3 + 1] = 255;
+ }
+ }
+ WriteTGAFile ("cbStats.tga", imgWidth, imgHeight, imgData);
+ delete[] imgData;
+ printf_console (" =%i uploads, %.1fkB traffic\n\n", uploads, traffic);
+ }
+
+ // reset stats
+ for (size_t i = 0; i < m_Buffers.size(); ++i)
+ {
+ ConstBuffer& cb = m_Buffers[i];
+ int cbSize = (m_BufferKeys[i]>>16);
+ cb.statsDirty = cb.statsVS = cb.statsPS = 0;
+ memset (cb.changeCounts, 0, cbSize/4*4);
+ memset (cb.tryCounts, 0, cbSize/4*4);
+ }
+}
+#endif
+
+#endif \ No newline at end of file
diff --git a/Runtime/GfxDevice/opengles30/ConstantBuffersGLES30.h b/Runtime/GfxDevice/opengles30/ConstantBuffersGLES30.h
new file mode 100644
index 0000000..a22eba2
--- /dev/null
+++ b/Runtime/GfxDevice/opengles30/ConstantBuffersGLES30.h
@@ -0,0 +1,72 @@
+#pragma once
+
+#include "IncludesGLES30.h"
+#include "Runtime/GfxDevice/GfxDeviceTypes.h"
+
+
+#define DEBUG_GLES30_CONSTANT_BUFFER_STATS 0
+
+#if DEBUG_GLES30_CONSTANT_BUFFER_STATS
+#include <map>
+#endif
+
+
+class ConstantBuffersGLES30
+{
+public:
+ ConstantBuffersGLES30();
+ ~ConstantBuffersGLES30() { Clear(); }
+
+ void Clear();
+
+ struct ConstBuffer {
+ int bindIndex;
+ bool dirty;
+ UInt8* data;
+ UInt32 buffer;
+ #if DEBUG_GLES30_CONSTANT_BUFFER_STATS
+ int statsDirty;
+ int stats
+ int* tryCounts;
+ int* changeCounts;
+ #endif
+ };
+
+ void SetCBInfo (int id, int size);
+ int FindAndBindCB (int id, int bind, int size);
+ void ResetBinds ();
+
+ void SetBuiltinCBConstant (int id, int offset, const void* data, int size);
+ void SetCBConstant (int index, int offset, const void* data, int size);
+
+ void UpdateBuffers();
+ void NewFrame();
+
+private:
+ inline int GetCBIndexByID (int id) const
+ {
+ UInt32 key = id;
+ int n = m_BufferKeys.size();
+ for (int i = 0; i < n; ++i)
+ {
+ if ((m_BufferKeys[i]&0xFFFF) == key)
+ return i;
+ }
+ Assert (false);
+ return -1;
+ }
+
+private:
+ typedef std::vector<UInt32> ConstBufferKeys;
+ typedef std::vector<ConstBuffer> ConstBuffers;
+ ConstBufferKeys m_BufferKeys;
+ ConstBuffers m_Buffers;
+
+ UInt32 m_ActiveBuffers[16];
+};
+
+
+#if !DEBUG_GLES30_CONSTANT_BUFFER_STATS
+inline void ConstantBuffersGLES30::NewFrame() { }
+#endif
+
diff --git a/Runtime/GfxDevice/opengles30/ContextGLES30.cpp b/Runtime/GfxDevice/opengles30/ContextGLES30.cpp
new file mode 100644
index 0000000..812d81b
--- /dev/null
+++ b/Runtime/GfxDevice/opengles30/ContextGLES30.cpp
@@ -0,0 +1,288 @@
+#include "UnityPrefix.h"
+#include "ContextGLES30.h"
+#include "IncludesGLES30.h"
+#include "AssertGLES30.h"
+#include "Runtime/Graphics/ScreenManager.h"
+#include "Runtime/Misc/QualitySettings.h"
+
+#if GFX_SUPPORTS_OPENGLES30
+
+#if UNITY_WIN
+
+#define EGL_OPENGL_ES3_BIT_KHR 0x00000040
+
+
+struct EGLESData
+{
+ void* dsp;
+ void* cfg;
+ void* cxt;
+ void* surf;
+ EGLESData():dsp(NULL),cfg(NULL),cxt(NULL),surf(NULL){}
+};
+
+
+static EGLESData sOpenGLESData;
+
+bool InitializeGLES30 ()
+{
+ HWND hwnd = GetScreenManager().GetWindow();
+ if (hwnd)
+ {
+ CreateContextGLES30 (hwnd);
+ return true;
+ }
+ ErrorString ("gles20: Can't initialize because HWND not set up");
+ return false;
+}
+void ShutdownGLES30 ()
+{
+ DestroyContextGLES30();
+}
+bool IsContextGLES30Created()
+{
+ return sOpenGLESData.surf != NULL &&
+ sOpenGLESData.cxt != NULL &&
+ sOpenGLESData.cfg != NULL &&
+ sOpenGLESData.dsp != NULL;
+}
+
+bool CreateContextGLES30(HWND hWnd)
+{
+ //Just in case
+ DestroyContextGLES30();
+
+ EGLint numConfigs;
+ EGLint majorVersion;
+ EGLint minorVersion;
+
+#if UNITY_WIN
+
+ int sampleCount = GetQualitySettings().GetCurrent().antiAliasing;
+
+ /// Build up the attribute list
+ const EGLint configAttribs[] =
+ {
+ EGL_LEVEL, 0,
+ EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
+ EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT_KHR,
+ EGL_NATIVE_RENDERABLE, EGL_FALSE,
+ EGL_DEPTH_SIZE, 16,
+ EGL_ALPHA_SIZE, 1,
+ EGL_STENCIL_SIZE, 1,
+ EGL_SAMPLES, sampleCount,
+ EGL_NONE
+ };
+
+ // Get Display
+ sOpenGLESData.dsp = eglGetDisplay( hWnd?GetDC(hWnd):EGL_DEFAULT_DISPLAY );
+ if ( sOpenGLESData.dsp == EGL_NO_DISPLAY )
+ {
+ printf_console("GLES30: eglGetDisplay failed\n" );
+ return false;
+ }
+ //Hack : eglInitialize invokes WM_ACTIVATE message, and gAppActive is already true, so Unity will try to call some functions which requires some initialization,
+ // and this is not done yet
+ extern bool gAlreadyClosing;
+ bool last = gAlreadyClosing;
+ gAlreadyClosing = true;
+ // Initialize EGL
+ if ( ! eglInitialize( sOpenGLESData.dsp, &majorVersion, &minorVersion) )
+ {
+ printf_console("GLES30: eglInitialize failed\n");
+ return false;
+ }
+
+
+ // Choose config
+ if ( !eglChooseConfig(sOpenGLESData.dsp, configAttribs, &sOpenGLESData.cfg, 1, &numConfigs) )
+ {
+ printf_console("GLES30: eglChooseConfig failed\n");
+ return false;
+ }
+
+
+ // Create a surface
+ sOpenGLESData.surf = eglCreateWindowSurface( sOpenGLESData.dsp, sOpenGLESData.cfg, NativeWindowType( hWnd ), NULL );
+ if ( sOpenGLESData.surf == EGL_NO_SURFACE )
+ {
+ printf_console("GLES30: eglCreateWindowSurface failed\n");
+ return false;
+ }
+
+ // Create a GL context
+ EGLint ctxAttribList[] = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE};
+ sOpenGLESData.cxt = eglCreateContext( sOpenGLESData.dsp, sOpenGLESData.cfg, EGL_NO_CONTEXT, ctxAttribList );
+ if ( sOpenGLESData.cxt == EGL_NO_CONTEXT )
+ {
+ printf_console("GLES30: eglCreateContext failed\n");
+ return false;
+ }
+
+ // Make the context current
+ if ( ! eglMakeCurrent( sOpenGLESData.dsp, sOpenGLESData.surf, sOpenGLESData.surf, sOpenGLESData.cxt ) )
+ {
+ printf_console("GLES30: eglMakeCurrent failed\n");
+ return false;
+ }
+
+ gAlreadyClosing = last;
+#endif
+
+ GLESAssert();
+
+ return true;
+}
+
+
+void DestroyContextGLES30()
+{
+ if(sOpenGLESData.dsp)
+ {
+ eglMakeCurrent(sOpenGLESData.dsp, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT) ;
+ eglDestroyContext( sOpenGLESData.dsp, sOpenGLESData.cxt );
+ eglDestroySurface( sOpenGLESData.dsp, sOpenGLESData.surf );
+ eglTerminate( sOpenGLESData.dsp);
+ }
+ sOpenGLESData.surf = NULL;
+ sOpenGLESData.cxt = NULL;
+ sOpenGLESData.cfg = NULL;
+ sOpenGLESData.dsp = NULL;
+}
+void PresentContextGLES30()
+{
+ eglSwapBuffers( sOpenGLESData.dsp, sOpenGLESData.surf );
+}
+
+void AcquireGLES30Context()
+{
+ if(sOpenGLESData.dsp)
+ {
+ // Make the context current
+ if ( ! eglMakeCurrent( sOpenGLESData.dsp, sOpenGLESData.surf, sOpenGLESData.surf, sOpenGLESData.cxt ) )
+ {
+ printf_console("GLES30: eglMakeCurrent failed\n");
+ }
+ }
+}
+
+void ReleaseGLES30Context()
+{
+ if(sOpenGLESData.dsp)
+ {
+ eglMakeCurrent(sOpenGLESData.dsp, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT) ;
+ }
+}
+
+
+#elif UNITY_LINUX
+
+static EGLDisplay gEGLDisplay = EGL_NO_DISPLAY;
+static EGLConfig gEGLConfig;
+static EGLSurface gEGLSurface = EGL_NO_SURFACE;
+static EGLContext gEGLContext = EGL_NO_CONTEXT;
+
+void SetEGLDisplay(EGLDisplay display)
+{
+ gEGLDisplay = display;
+}
+
+void SetEGLConfig(const EGLConfig &config)
+{
+ gEGLConfig = config;
+}
+
+bool InitializeGLES30 ()
+{
+ Window window = 0;
+ window = GetScreenManager().GetWindow();
+
+ if(window)
+ {
+ CreateContextGLES30(window);
+ return true;
+ }
+
+ return false;
+}
+
+void ShutdownGLES30 ()
+{
+ DestroyContextGLES30();
+}
+
+bool IsContextGLES30Created()
+{
+ return gEGLSurface != EGL_NO_SURFACE && gEGLContext != EGL_NO_CONTEXT;
+}
+
+bool CreateContextGLES30(Window window)
+{
+ DestroyContextGLES30();
+
+ gEGLSurface = eglCreateWindowSurface(gEGLDisplay, gEGLConfig, window, NULL);
+ if(gEGLSurface == EGL_NO_SURFACE)
+ {
+ printf_console("eglCreateWindowSurface failed\n");
+ return false;
+ }
+
+ // Create a context
+ printf_console("Creating context\n");
+ EGLint ctxAttribList[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};
+ gEGLContext = eglCreateContext(gEGLDisplay, gEGLConfig, EGL_NO_CONTEXT, ctxAttribList );
+ if ( gEGLContext == EGL_NO_CONTEXT )
+ {
+ printf_console( "eglCreateContext failed\n" );
+ return false;
+ }
+
+ if(!eglMakeCurrent(gEGLDisplay, gEGLSurface, gEGLSurface, gEGLContext))
+ {
+ printf_console("eglMakeCurrent failed\n");
+ }
+
+ return true;
+}
+
+void DestroyContextGLES30()
+{
+ if(IsContextGLES30Created())
+ {
+ eglMakeCurrent(gEGLDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+ eglDestroyContext(gEGLDisplay, gEGLContext);
+ eglDestroySurface(gEGLDisplay, gEGLSurface);
+ }
+
+ gEGLSurface = EGL_NO_SURFACE;
+ gEGLContext = EGL_NO_CONTEXT;
+}
+
+void PresentContextGLES()
+{
+ eglSwapBuffers(gEGLDisplay, gEGLSurface);
+}
+
+void AcquireGLES30Context()
+{
+ if(gEGLDisplay)
+ {
+ // Make the context current
+ if(!eglMakeCurrent(gEGLDisplay, gEGLSurface, gEGLSurface, gEGLContext))
+ {
+ printf_console("GLES30: eglMakeCurrent failed\n");
+ }
+ }
+}
+
+void ReleaseGLES30Context()
+{
+ if(gEGLDisplay)
+ {
+ eglMakeCurrent(gEGLDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+ }
+}
+
+
+#endif // UNITY_WIN
+#endif // GFX_SUPPORTS_OPENGLES30
diff --git a/Runtime/GfxDevice/opengles30/ContextGLES30.h b/Runtime/GfxDevice/opengles30/ContextGLES30.h
new file mode 100644
index 0000000..c509f57
--- /dev/null
+++ b/Runtime/GfxDevice/opengles30/ContextGLES30.h
@@ -0,0 +1,37 @@
+#pragma once
+
+#include "Runtime/GfxDevice/GfxDeviceObjects.h"
+#include "Runtime/GfxDevice/GfxDeviceTypes.h"
+
+#if UNITY_LINUX
+#include <X11/Xlib.h>
+#include <GLES3/gl2.h>
+#endif
+
+#if UNITY_BB10
+#include <GLES3/gl2.h>
+#include <screen/screen.h>
+#endif
+
+#if UNITY_WIN || UNITY_LINUX || UNITY_BB10 || UNITY_ANDROID
+bool InitializeGLES30 ();
+void ShutdownGLES30 ();
+bool IsContextGLES30Created();
+#if UNITY_WIN
+bool CreateContextGLES30(HWND hWnd);
+#elif UNITY_LINUX
+bool CreateContextGLES30(Window window);
+#elif UNITY_BB10
+bool CreateContextGLES30(screen_window_t window);
+void ResizeContextGLES30(screen_window_t window, int width, int height);
+#endif
+void DestroyContextGLES30();
+#endif
+void PresentContextGLES();
+void PresentContextGLES30();
+
+void ReleaseGLES30Context();
+
+void AcquireGLES30Context();
+
+
diff --git a/Runtime/GfxDevice/opengles30/DataBuffersGLES30.cpp b/Runtime/GfxDevice/opengles30/DataBuffersGLES30.cpp
new file mode 100644
index 0000000..24484d7
--- /dev/null
+++ b/Runtime/GfxDevice/opengles30/DataBuffersGLES30.cpp
@@ -0,0 +1,429 @@
+#include "UnityPrefix.h"
+#include "DataBuffersGLES30.h"
+
+#if GFX_SUPPORTS_OPENGLES30
+
+#include "AssertGLES30.h"
+#include "IncludesGLES30.h"
+#include "Runtime/GfxDevice/GLDataBufferCommon.h"
+
+#include <algorithm>
+
+#if 1
+ #define DBG_LOG_BUF_GLES30(...) {}
+#else
+ #define DBG_LOG_BUF_GLES30(...) {printf_console(__VA_ARGS__);printf_console("\n");}
+#endif
+
+static const float kBufferAllocateWeight = 10.0f; //! Default weight for buffer allocation from scratch.
+static const float kBufferSizeWeightFactor = 1.f/8096.f; //!< Weight factor for size difference.
+static const float kBufferUsageDiffWeight = 8.f; //!< Weight factor for usage difference.
+
+static const float kBufferDeleteSizeWeightFactor = 1.f/16384.f; //!< Deleting scale factor.
+static const float kBufferDeleteMaxWeightFactor = 300.0f; //!< ...
+static const float kBufferDeleteMinWeight = 30.0f; //!< Required weight for deleting buffer.
+
+static const UInt32 kBufferPruneFrequency = 10; //!< Unneeded buffers are pruned once per kBufferPruneFrequency frames.
+
+bool BufferUpdateCausesStallGLES30 (const DataBufferGLES30* buffer)
+{
+ // \note If needed, the min age should me made ES3 specific graphics capability.
+ return buffer->GetRenderAge() < kBufferUpdateMinAgeGLES30;
+}
+
+//! Computes weight for sorting buffers
+static float ComputeBufferWeight (const DataBufferGLES30* buffer, int desiredSize, UInt32 desiredUsage)
+{
+ const int bufferSize = buffer->GetSize();
+ const UInt32 bufferUsage = buffer->GetUsage();
+
+ if (buffer->GetSize() == 0)
+ return kBufferAllocateWeight;
+
+ const int sizeDiff = std::abs(bufferSize-desiredSize);
+
+ return float(sizeDiff)*kBufferSizeWeightFactor + ((bufferUsage != desiredUsage) ? kBufferUsageDiffWeight : 0.f);
+}
+
+//! Compute weight for eliminating old buffers.
+static float ComputeBufferDeleteWeight (const DataBufferGLES30* buffer)
+{
+ // \todo [2013-05-13 pyry] Take into account current memory pressure and buffer size.
+ const UInt32 renderAge = buffer->GetRenderAge();
+ return float(renderAge) - std::min(kBufferDeleteMaxWeightFactor, float(buffer->GetSize())*kBufferDeleteSizeWeightFactor);
+}
+
+struct WeightedBufferGLES3
+{
+ int sizeClass;
+ int bufferNdx;
+ float weight;
+
+ WeightedBufferGLES3 (int sizeClass_, int bufferNdx_, float weight_)
+ : sizeClass (sizeClass_)
+ , bufferNdx (bufferNdx_)
+ , weight (weight_)
+ {
+ }
+};
+
+struct CompareWeightsLess
+{
+ inline bool operator() (const WeightedBufferGLES3& a, const WeightedBufferGLES3& b) const
+ {
+ return a.weight < b.weight;
+ }
+};
+
+struct CompareWeightsGreater
+{
+ inline bool operator() (const WeightedBufferGLES3& a, const WeightedBufferGLES3& b) const
+ {
+ return a.weight > b.weight;
+ }
+};
+
+// DataBufferGLES30
+
+static const GLenum kBufferTarget = GL_COPY_WRITE_BUFFER; //!< Target used for buffer operations.
+
+DataBufferGLES30::DataBufferGLES30 (BufferManagerGLES30& bufferManager)
+ : m_manager (bufferManager)
+ , m_buffer (0)
+ , m_size (0)
+ , m_usage (0)
+ , m_lastRecreated (0)
+ , m_lastUpdated (0)
+ , m_lastRendered (0)
+{
+ GLES_CHK(glGenBuffers(1, (GLuint*)&m_buffer));
+}
+
+DataBufferGLES30::~DataBufferGLES30 (void)
+{
+ if (m_buffer)
+ glDeleteBuffers(1, (GLuint*)&m_buffer);
+}
+
+void DataBufferGLES30::Disown (void)
+{
+ m_buffer = 0;
+}
+
+void DataBufferGLES30::Release (void)
+{
+ m_manager.ReleaseBuffer(this);
+}
+
+void DataBufferGLES30::RecreateStorage (int size, UInt32 usage)
+{
+ RecreateWithData(size, usage, 0);
+}
+
+void DataBufferGLES30::RecreateWithData (int size, UInt32 usage, const void* data)
+{
+ glBindBuffer(kBufferTarget, m_buffer);
+ glBufferData(kBufferTarget, size, data, usage);
+ glBindBuffer(kBufferTarget, 0);
+ GLESAssert();
+
+ RecordRecreate(size, usage);
+}
+
+void DataBufferGLES30::Upload (int offset, int size, const void* data)
+{
+ glBindBuffer(kBufferTarget, m_buffer);
+ glBufferSubData(kBufferTarget, offset, size, data);
+ glBindBuffer(kBufferTarget, 0);
+ GLESAssert();
+
+ RecordUpdate();
+}
+
+void* DataBufferGLES30::Map (int offset, int size, UInt32 mapBits)
+{
+ glBindBuffer(kBufferTarget, m_buffer);
+ void* ptr = glMapBufferRange(kBufferTarget, offset, size, mapBits);
+ glBindBuffer(kBufferTarget, 0);
+ GLESAssert();
+
+ return ptr;
+}
+
+void DataBufferGLES30::FlushMappedRange (int offset, int size)
+{
+ glBindBuffer(kBufferTarget, m_buffer);
+ glFlushMappedBufferRange(kBufferTarget, offset, size);
+ glBindBuffer(kBufferTarget, 0);
+ GLESAssert();
+}
+
+void DataBufferGLES30::Unmap (void)
+{
+ glBindBuffer(kBufferTarget, m_buffer);
+ glUnmapBuffer(kBufferTarget);
+ glBindBuffer(kBufferTarget, 0);
+ GLESAssert();
+}
+
+void DataBufferGLES30::RecordRecreate (int size, UInt32 usage)
+{
+ if (BufferUpdateCausesStallGLES30(this))
+ DBG_LOG_BUF_GLES30("DataBufferGLES30: Warning: buffer with render age %u was recreated!", GetRenderAge());
+
+ m_size = size;
+ m_usage = usage;
+ m_lastRecreated = m_manager.GetFrameIndex();
+
+ // Update GFX mem allocation stats
+ REGISTER_EXTERNAL_GFX_DEALLOCATION(MAKE_DATA_BUFFER_ID(m_buffer));
+ REGISTER_EXTERNAL_GFX_ALLOCATION_REF(MAKE_DATA_BUFFER_ID(m_buffer), size, this);
+}
+
+void DataBufferGLES30::RecordUpdate (void)
+{
+ if (BufferUpdateCausesStallGLES30(this))
+ DBG_LOG_BUF_GLES30("DataBufferGLES30: Warning: buffer with render age %u was updated!", GetRenderAge());
+
+ m_lastUpdated = m_manager.GetFrameIndex();
+}
+
+void DataBufferGLES30::RecordRender (void)
+{
+ m_lastRendered = m_manager.GetFrameIndex();
+}
+
+// \note Overflow is perfectly ok here.
+
+UInt32 DataBufferGLES30::GetRecreateAge (void) const
+{
+ return m_manager.GetFrameIndex() - m_lastRecreated;
+}
+
+UInt32 DataBufferGLES30::GetUpdateAge (void) const
+{
+ return m_manager.GetFrameIndex() - m_lastUpdated;
+}
+
+UInt32 DataBufferGLES30::GetRenderAge (void) const
+{
+ return m_manager.GetFrameIndex() - m_lastRendered;
+}
+
+// BufferManagerGLES30
+
+BufferManagerGLES30::BufferManagerGLES30 (void)
+ : m_frameIndex(0)
+{
+}
+
+BufferManagerGLES30::~BufferManagerGLES30 (void)
+{
+ Clear();
+}
+
+void BufferManagerGLES30::Clear (void)
+{
+ for (std::vector<DataBufferGLES30*>::iterator i = m_pendingBuffers.begin(); i != m_pendingBuffers.end(); i++)
+ delete *i;
+ m_pendingBuffers.clear();
+
+ for (int ndx = 0; ndx < kSizeClassCount; ndx++)
+ {
+ for (std::vector<DataBufferGLES30*>::iterator i = m_liveBuffers[ndx].begin(); i != m_liveBuffers[ndx].end(); i++)
+ delete *i;
+ m_liveBuffers[ndx].clear();
+ }
+}
+
+inline int BufferManagerGLES30::GetSizeClass (int size)
+{
+ for (int ndx = 0; ndx < kSizeClassCount; ndx++)
+ {
+ if (size < GetSizeClassLimit(ndx))
+ return ndx;
+ }
+ Assert(false);
+ return 0;
+}
+
+DataBufferGLES30* BufferManagerGLES30::AcquireBuffer (int size, UInt32 usage)
+{
+ const float maxWeight = kBufferAllocateWeight;
+ const int maxCandidates = 5; // Number of potential candidates to consider actually.
+
+ const int sizeClass = GetSizeClass(size); // Try only that size class
+ int numCandidates = 0; // Number of potential candidates considered
+ int bestBufferNdx = -1;
+ float bestWeight = std::numeric_limits<float>::infinity();
+
+ for (int bufferNdx = 0; bufferNdx < (int)m_liveBuffers[sizeClass].size(); bufferNdx++)
+ {
+ DataBufferGLES30* buffer = m_liveBuffers[sizeClass][bufferNdx];
+ const float weight = ComputeBufferWeight(buffer, size, usage);
+
+ if (weight <= maxWeight && weight < bestWeight)
+ {
+ bestBufferNdx = bufferNdx;
+ bestWeight = weight;
+
+ numCandidates += 1;
+ }
+
+ if (numCandidates >= maxCandidates)
+ break; // Do not try other buffers, sorry.
+ }
+
+ DBG_LOG_BUF_GLES30("BufferManagerGLES30::AcquireBuffer(%d, 0x%04x): tried %d candidates", size, usage, numCandidates);
+
+ if (bestBufferNdx >= 0)
+ {
+ const int bufferNdx = bestBufferNdx;
+ DataBufferGLES30* selectedBuffer = m_liveBuffers[sizeClass][bufferNdx];
+
+ if (bufferNdx+1 != m_liveBuffers[sizeClass].size())
+ std::swap(m_liveBuffers[sizeClass][bufferNdx], m_liveBuffers[sizeClass].back());
+ m_liveBuffers[sizeClass].pop_back();
+
+ DBG_LOG_BUF_GLES30(" => selected buffer [%d]%d, weight = %f", sizeClass, bufferNdx, ComputeBufferWeight(selectedBuffer, size, usage));
+ return selectedBuffer;
+ }
+ else
+ {
+ DBG_LOG_BUF_GLES30(" => creating new buffer");
+ return new DataBufferGLES30(*this);
+ }
+}
+
+void BufferManagerGLES30::ReleaseBuffer (DataBufferGLES30* buffer)
+{
+ if (!BufferUpdateCausesStallGLES30(buffer))
+ InsertIntoLive(buffer);
+ else
+ m_pendingBuffers.push_back(buffer);
+}
+
+void BufferManagerGLES30::AdvanceFrame (void)
+{
+ m_frameIndex += 1; // \note Overflow is ok.
+
+ UpdateLiveSetFromPending();
+
+ // \todo [2013-05-13 pyry] Do we want to do pruning somewhere else as well?
+ if ((m_frameIndex % kBufferPruneFrequency) == 0)
+ PruneFreeBuffers();
+}
+
+void BufferManagerGLES30::UpdateLiveSetFromPending (void)
+{
+ int bufNdx = 0;
+ while (bufNdx < (int)m_pendingBuffers.size())
+ {
+ if (!BufferUpdateCausesStallGLES30(m_pendingBuffers[bufNdx]))
+ {
+ DataBufferGLES30* newLiveBuffer = m_pendingBuffers[bufNdx];
+
+ if (bufNdx+1 != m_pendingBuffers.size())
+ std::swap(m_pendingBuffers[bufNdx], m_pendingBuffers.back());
+ m_pendingBuffers.pop_back();
+
+ InsertIntoLive(newLiveBuffer);
+ // \note bufNdx now contains a new buffer and it must be processed as well. Thus bufNdx is not incremented.
+ }
+ else
+ bufNdx += 1;
+ }
+}
+
+void BufferManagerGLES30::InsertIntoLive (DataBufferGLES30* buffer)
+{
+ const int bufSize = buffer->GetSize();
+ const int sizeClass = GetSizeClass(bufSize);
+
+ m_liveBuffers[sizeClass].push_back(buffer);
+}
+
+UInt32 BufferManagerGLES30::GetTotalFreeSize (void)
+{
+ UInt32 totalBytes = 0;
+
+ for (std::vector<DataBufferGLES30*>::const_iterator bufIter = m_pendingBuffers.begin(); bufIter != m_pendingBuffers.end(); ++bufIter)
+ totalBytes += (*bufIter)->GetSize();
+
+ for (int ndx = 0; ndx < kSizeClassCount; ndx++)
+ {
+ for (std::vector<DataBufferGLES30*>::const_iterator bufIter = m_liveBuffers[ndx].begin(); bufIter != m_liveBuffers[ndx].end(); ++bufIter)
+ totalBytes += (*bufIter)->GetSize();
+ }
+
+ return totalBytes;
+}
+
+void BufferManagerGLES30::PruneFreeBuffers (void)
+{
+ const UInt32 numBytesInFreeList = GetTotalFreeSize();
+ DBG_LOG_BUF_GLES30("BufferManagerGLES30: %u B / %.2f MiB in free buffers", numBytesInFreeList, float(numBytesInFreeList) / float(1<<20));
+
+ // \todo [2013-05-13 pyry] Do this properly - take into account allocated memory size.
+
+ UInt32 numBytesFreed = 0;
+ int numBuffersDeleted = 0;
+
+ // \note pending buffers are ignored. They will end up in live soon anyway.
+ for (int sizeClass = 0; sizeClass < kSizeClassCount; sizeClass++)
+ {
+ int bufNdx = 0;
+ while (bufNdx < m_liveBuffers[sizeClass].size())
+ {
+ DataBufferGLES30* buffer = m_liveBuffers[sizeClass][bufNdx];
+ const float weight = ComputeBufferDeleteWeight(buffer);
+
+ if (weight >= kBufferDeleteMinWeight)
+ {
+ if (bufNdx+1 != m_liveBuffers[sizeClass].size())
+ std::swap(m_liveBuffers[sizeClass][bufNdx], m_liveBuffers[sizeClass].back());
+ m_liveBuffers[sizeClass].pop_back();
+
+ numBytesFreed += buffer->GetSize();
+ numBuffersDeleted += 1;
+
+ delete buffer;
+ }
+ else
+ bufNdx += 1;
+ }
+ }
+
+ DBG_LOG_BUF_GLES30(" => freed %d buffers, %u B / %.2f MiB", numBuffersDeleted, numBytesFreed, float(numBytesFreed) / float(1<<20));
+}
+
+void BufferManagerGLES30::InvalidateAll (void)
+{
+ for (std::vector<DataBufferGLES30*>::iterator iter = m_pendingBuffers.begin(); iter != m_pendingBuffers.end(); ++iter)
+ {
+ (*iter)->Disown();
+ delete *iter;
+ }
+ m_pendingBuffers.clear();
+
+ for (int classNdx = 0; classNdx < kSizeClassCount; classNdx++)
+ {
+ for (std::vector<DataBufferGLES30*>::iterator iter = m_liveBuffers[classNdx].begin(); iter != m_liveBuffers[classNdx].end(); ++iter)
+ {
+ (*iter)->Disown();
+ delete *iter;
+ }
+ m_liveBuffers[classNdx].clear();
+ }
+}
+
+BufferManagerGLES30* g_bufferManager = 0;
+
+BufferManagerGLES30* GetBufferManagerGLES30 (void)
+{
+ if (!g_bufferManager)
+ g_bufferManager = new BufferManagerGLES30();
+ return g_bufferManager;
+}
+
+#endif // GFX_SUPPORTS_OPENGLES30
diff --git a/Runtime/GfxDevice/opengles30/DataBuffersGLES30.h b/Runtime/GfxDevice/opengles30/DataBuffersGLES30.h
new file mode 100644
index 0000000..524dc53
--- /dev/null
+++ b/Runtime/GfxDevice/opengles30/DataBuffersGLES30.h
@@ -0,0 +1,137 @@
+#pragma once
+
+#include "Configuration/UnityConfigure.h"
+#include "Runtime/GfxDevice/GfxDeviceConfigure.h"
+
+#if GFX_SUPPORTS_OPENGLES30
+
+#include "Runtime/GfxDevice/GfxDeviceObjects.h"
+#include "Runtime/GfxDevice/GfxDeviceTypes.h"
+
+class BufferManagerGLES30;
+
+enum
+{
+ kBufferUpdateMinAgeGLES30 = 2 //!< This many frames must be elapsed since last render before next buffer update. \todo [2013-05-31 pyry] From GfxDevice caps
+};
+
+class DataBufferGLES30
+{
+public:
+ DataBufferGLES30 (BufferManagerGLES30& bufferManager);
+ ~DataBufferGLES30 (void);
+
+ void Release (void); //!< Release to BufferManager.
+
+ UInt32 GetBuffer (void) const { return m_buffer; }
+ int GetSize (void) const { return m_size; }
+ UInt32 GetUsage (void) const { return m_usage; }
+
+ void RecreateStorage (int size, UInt32 usage);
+ void RecreateWithData (int size, UInt32 usage, const void* data);
+
+ void Upload (int offset, int size, const void* data);
+
+ void* Map (int offset, int size, UInt32 mapBits);
+ void FlushMappedRange (int offset, int size);
+ void Unmap (void);
+
+ void RecordRecreate (int size, UInt32 usage); //!< Updates storage parameters and recreate time.
+ void RecordUpdate (void); //!< Updates last update time if buffer was updated manually.
+ void RecordRender (void); //!< Updates last render time.
+
+ UInt32 GetRecreateAge (void) const;
+ UInt32 GetUpdateAge (void) const;
+ UInt32 GetRenderAge (void) const;
+
+ //! Disown and remove buffer handle. Used if destructor should not try to delete buffer..
+ void Disown (void);
+
+private:
+ DataBufferGLES30 (const DataBufferGLES30& other);
+ DataBufferGLES30& operator= (const DataBufferGLES30& other);
+
+ BufferManagerGLES30& m_manager;
+ UInt32 m_buffer;
+ int m_size;
+ UInt32 m_usage;
+
+ // \note Always used to compute relative age and overflow is handled
+ // in computation. Thus frame index can safely overflow.
+ UInt32 m_lastRecreated; //!< Last recreated.
+ UInt32 m_lastUpdated; //!< Frame index when last updated.
+ UInt32 m_lastRendered; //!< Frame index when last rendered.
+};
+
+// BufferManager
+//
+// BufferManager is responsible for allocating and maintaining list of free buffer objects that
+// could be recycled later on. Buffers are either allocated or recycled based on their properties.
+// Most important property for proper use of buffers is to make sure they are not recycled
+// too soon after using them for rendering.
+//
+// BufferManager is only responsible for managing currently free buffers. So user must either
+// release or destroy buffer objects manually. User is also responsible of implementing sane
+// usage patterns for buffers that it owns (for example not updating data right after buffer
+// has been submitted for rendering).
+//
+// Buffers are associated to the BufferManager that was used to create them. Thus user must either
+// destroy buffer, or release it back to same BufferManager.
+//
+// The best usage pattern for leveraging BufferManager is to always release buffers when there
+// is no longer need to preserve the data in buffer object. That way BufferManager takes care
+// of recycling buffer when it is appropriate.
+
+class BufferManagerGLES30
+{
+public:
+ BufferManagerGLES30 (void);
+ ~BufferManagerGLES30 (void);
+
+ //! Acquire a new or recycled buffer. Returns either buffer object that can fit data, or empty buffer (GetSize() == 0).
+ DataBufferGLES30* AcquireBuffer (int size, UInt32 usage);
+ void ReleaseBuffer (DataBufferGLES30* buffer);
+
+ void AdvanceFrame (void); //!< Advance frame index. Must be called at the end of frame.
+ UInt32 GetFrameIndex (void) const { return m_frameIndex; }
+
+ //!< Invalidate all owned buffers. Used on context loss.
+ void InvalidateAll (void);
+
+private:
+ BufferManagerGLES30 (const BufferManagerGLES30& other);
+ BufferManagerGLES30& operator= (const BufferManagerGLES30& other);
+
+ void Clear (void);
+
+ void PruneFreeBuffers (void);
+ UInt32 GetTotalFreeSize (void);
+
+ static inline int GetSizeClassLimit (int classNdx) { return classNdx+1 < kSizeClassCount ? (1<<(classNdx*kSizeClassStepLog2 + kSizeClassBaseLog2)) : INT_MAX; }
+
+ void UpdateLiveSetFromPending(void);
+ void InsertIntoLive (DataBufferGLES30* buffer);
+ static int GetSizeClass (int bufSize);
+
+ UInt32 m_frameIndex; //!< Frame index for computing buffer ages.
+
+ enum
+ {
+ kSizeClassBaseLog2 = 10,
+ kSizeClassStepLog2 = 1,
+ kSizeClassCount = 7
+ };
+
+ // Buffers that can not be selected are in pendingBuffers. Live buffers contain
+ // buffers organized by size into kSizeClassCount classes.
+ std::vector<DataBufferGLES30*> m_pendingBuffers;
+ std::vector<DataBufferGLES30*> m_liveBuffers[kSizeClassCount];
+};
+
+// \todo [2013-05-10 pyry] Do not use singletons...
+BufferManagerGLES30* GetBufferManagerGLES30 (void);
+
+//! Determine if buffer update will likely cause GPU stall.
+bool BufferUpdateCausesStallGLES30 (const DataBufferGLES30* buffer);
+
+#endif // GFX_SUPPORTS_OPENGLES30
diff --git a/Runtime/GfxDevice/opengles30/DebugGLES30.cpp b/Runtime/GfxDevice/opengles30/DebugGLES30.cpp
new file mode 100644
index 0000000..af5efad
--- /dev/null
+++ b/Runtime/GfxDevice/opengles30/DebugGLES30.cpp
@@ -0,0 +1,38 @@
+#include "UnityPrefix.h"
+#include "IncludesGLES30.h"
+#include "DebugGLES30.h"
+#include "AssertGLES30.h"
+
+
+void DumpVertexArrayStateGLES30()
+{
+#if GFX_SUPPORTS_OPENGLES30
+ GLint maxVertexAttribs = 0;
+ GLES_CHK(glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &maxVertexAttribs));
+
+ GLint vbo = 0;
+ GLint ibo = 0;
+ GLES_CHK(glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &vbo));
+ GLES_CHK(glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &ibo));
+
+ printf_console("---> VertexArray State: vbo:%d ibo:%d\n", vbo, ibo);
+
+ for (int q = 0; q < maxVertexAttribs; ++q)
+ {
+ int enabled, size, stride, normalized, type, vbo;
+ GLES_CHK(glGetVertexAttribiv(q, GL_VERTEX_ATTRIB_ARRAY_ENABLED, &enabled));
+ GLES_CHK(glGetVertexAttribiv(q, GL_VERTEX_ATTRIB_ARRAY_SIZE, &size));
+ GLES_CHK(glGetVertexAttribiv(q, GL_VERTEX_ATTRIB_ARRAY_STRIDE, &stride));
+ GLES_CHK(glGetVertexAttribiv(q, GL_VERTEX_ATTRIB_ARRAY_NORMALIZED, &normalized));
+ GLES_CHK(glGetVertexAttribiv(q, GL_VERTEX_ATTRIB_ARRAY_TYPE, &type));
+ GLES_CHK(glGetVertexAttribiv(q, GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING, &vbo));
+
+ GLvoid* ptr;
+ GLES_CHK(glGetVertexAttribPointerv(q, GL_VERTEX_ATTRIB_ARRAY_POINTER, &ptr));
+
+ printf_console(" attr[%d] --- %s type:%d size:%d stride:%d norm:%d vbo:%d, %p\n",
+ q, enabled? "On ": "Off",
+ type, size, stride, normalized, vbo, ptr);
+ }
+#endif
+}
diff --git a/Runtime/GfxDevice/opengles30/DebugGLES30.h b/Runtime/GfxDevice/opengles30/DebugGLES30.h
new file mode 100644
index 0000000..0468cdf
--- /dev/null
+++ b/Runtime/GfxDevice/opengles30/DebugGLES30.h
@@ -0,0 +1,139 @@
+#ifndef DEBUGGLES30_H
+#define DEBUGGLES30_H
+
+#include "Runtime/Utilities/LogAssert.h"
+#include "Runtime/GfxDevice/GfxDeviceTypes.h"
+#include "Runtime/Math/Matrix4x4.h"
+
+
+void DumpVertexArrayStateGLES30();
+
+#if !UNITY_RELEASE
+ #define DBG_LOG_GLES30_ACTIVE 0
+ #define DBG_TEXTURE_VERBOSE_GLES30_ACTIVE 0
+ #define DBG_SHADER_VERBOSE_GLES30_ACTIVE 0
+ #define DBG_GLSL_BINDINGS_GLES30_ACTIVE 0
+#else
+ #define DBG_LOG_GLES30_ACTIVE UNITY_WEBGL
+ #define DBG_TEXTURE_VERBOSE_GLES30_ACTIVE UNITY_WEBGL
+ #define DBG_SHADER_VERBOSE_GLES30_ACTIVE 0
+ #define DBG_GLSL_BINDINGS_GLES30_ACTIVE 0
+#endif
+
+#if DBG_LOG_GLES30_ACTIVE
+ #define DBG_LOG_GLES30(...) {printf_console(__VA_ARGS__);printf_console("\n");}
+
+ inline std::string GetMatrixString(const Matrix4x4f& mtx)
+ {
+ return Format("%.2f, %.2f, %.2f, %2f,\n"
+ "%.2f, %.2f, %.2f, %2f,\n"
+ "%.2f, %.2f, %.2f, %2f,\n"
+ "%.2f, %.2f, %.2f, %2f",
+ mtx[0], mtx[1], mtx[2], mtx[3],
+ mtx[4], mtx[5], mtx[6], mtx[7],
+ mtx[8], mtx[9], mtx[10],mtx[11],
+ mtx[12],mtx[13],mtx[14],mtx[15]);
+ }
+
+ inline const char* GetBoolString(bool type)
+ {
+ return type?"True":"False";
+ }
+
+ inline const char * GetBlendModeString(BlendMode type)
+ {
+ switch(type)
+ {
+ case kBlendZero:return "kBlendZero";
+ case kBlendOne:return "kBlendOne";
+ case kBlendDstColor:return "kBlendDstColor";
+ case kBlendSrcColor:return "kBlendSrcColor";
+ case kBlendOneMinusDstColor:return "kBlendOneMinusDstColor";
+ case kBlendSrcAlpha:return "kBlendSrcAlpha";
+ case kBlendOneMinusSrcColor:return "kBlendOneMinusSrcColor";
+ case kBlendDstAlpha:return "kBlendDstAlpha";
+ case kBlendOneMinusDstAlpha:return "kBlendOneMinusDstAlpha";
+ case kBlendSrcAlphaSaturate:return "kBlendSrcAlphaSaturate";
+ case kBlendOneMinusSrcAlpha:return "kBlendOneMinusSrcAlpha";
+ default:return "GetBlendModeString<Undefined>";
+ }
+ }
+ inline const char * GetCullModeString(CullMode type)
+ {
+ switch(type)
+ {
+ case kCullUnknown:return "kCullUnknown";
+ case kCullOff:return "kCullOff:return";
+ case kCullFront:return "kCullFront";
+ case kCullBack:return "kCullBack";;
+ default:return "GetCullMode<undefined>";
+ }
+ }
+
+ inline const char * GetCompareFunctionString(CompareFunction type)
+ {
+ switch(type)
+ {
+ case kFuncUnknown:return "kFuncUnknown";
+ case kFuncDisabled:return "kFuncDisabled";
+ case kFuncNever:return "kFuncNever";
+ case kFuncLess:return "kFuncLess";
+ case kFuncEqual:return "kFuncEqual";
+ case kFuncLEqual:return "kFuncLEqual";
+ case kFuncGreater:return "kFuncGreater";
+ case kFuncNotEqual:return "kFuncNotEqual";
+ case kFuncGEqual:return "kFuncGEqual";
+ case kFuncAlways:return "kFuncAlways";
+ default:return "GetCompareFunctionString<Undefined>";
+ }
+ }
+
+ inline const char * GetShaderTypeString(ShaderType type)
+ {
+ switch(type)
+ {
+ case kShaderNone:return "kShaderNone";
+ case kShaderVertex:return "kShaderVertex";
+ case kShaderFragment:return "kShaderFragment";
+ default:return "GetShaderTypeString<undefined>";
+ }
+ }
+
+ inline const char * GetShaderImplTypeString(ShaderImplType type)
+ {
+ switch(type)
+ {
+ case kShaderImplUndefined: return "kShaderImplUndefined";
+ case kShaderImplVertex: return "kShaderImplVertex";
+ case kShaderImplFragment: return "kShaderImplFragment";
+ case kShaderImplBoth: return "kShaderImplBoth";
+ default:return "GetShaderImplTypeString<Undefined>";
+ }
+ }
+
+#else
+ #define DBG_LOG_GLES30(...)
+#endif
+
+#if DBG_TEXTURE_VERBOSE_GLES30_ACTIVE
+#define DBG_TEXTURE_VERBOSE_GLES30(...) {printf_console(__VA_ARGS__);printf_console("\n");}
+#else
+#define DBG_TEXTURE_VERBOSE_GLES30(...)
+#endif
+
+#if DBG_SHADER_VERBOSE_GLES30_ACTIVE
+ #define DBG_SHADER_VERBOSE_GLES30(...) {printf_console(__VA_ARGS__);printf_console("\n");}
+ #define DBG_SHADER_VERBOSE_GLES30_DUMP_SHADER(prefix, text) { printf_console("%s\n", prefix);DebugTextLineByLine(text);printf_console("\n---\n");}
+#else
+ #define DBG_SHADER_VERBOSE_GLES30(...)
+ #define DBG_SHADER_VERBOSE_GLES30_DUMP_SHADER(prefix, text)
+#endif
+
+#if DBG_GLSL_BINDINGS_GLES30_ACTIVE
+ #define DBG_GLSL_BINDINGS_GLES30(...) {printf_console(__VA_ARGS__);printf_console("\n");}
+#else
+ #define DBG_GLSL_BINDINGS_GLES30(...)
+#endif
+
+
+#endif
diff --git a/Runtime/GfxDevice/opengles30/FixedFunctionStateGLES30.cpp b/Runtime/GfxDevice/opengles30/FixedFunctionStateGLES30.cpp
new file mode 100644
index 0000000..de052b9
--- /dev/null
+++ b/Runtime/GfxDevice/opengles30/FixedFunctionStateGLES30.cpp
@@ -0,0 +1,71 @@
+#include "UnityPrefix.h"
+#include "FixedFunctionStateGLES30.h"
+#include <sstream>
+
+
+FixedFunctionStateGLES30::FixedFunctionStateGLES30 ()
+: texUnitCount(0),
+ lightType(0),
+ texUnitMatrix(0),
+ lightingEnabled(false),
+ specularEnabled(true),
+ onlyDirectionalLights(false),
+ lightCount(0),
+ useUniformInsteadOfVertexColor(false),
+ useVertexColorAsAmbientAndDiffuse(false),
+ useVertexColorAsEmission(false),
+ fogMode(kFogDisabled),
+ addSpecularAfterTexturing(false),
+ alphaTest(kFuncDisabled)
+{
+ for (int q = 0; q < kMaxSupportedTextureUnitsGLES; ++q)
+ {
+ texUnitCube[q] = false;
+ texUnitGen[q] = kTexGenDisabled;
+ texUnitColorCombiner[q] = ~0UL;
+ texUnitAlphaCombiner[q] = ~0UL;
+ }
+}
+
+static std::string CombinerToString (unsigned int combinerDesc)
+{
+ std::ostringstream s;
+ s << combinerDesc;
+ return s.str().c_str();
+}
+
+
+std::string FixedFunctionStateGLES30::ToString () const
+{
+ std::ostringstream s;
+
+ s << "FixedFunctionStateGLES30::ToString():\n";
+ s << " lightingEnabled = " << lightingEnabled << "\n";
+ s << " specularEnabled = " << specularEnabled << "\n";
+ s << " lights = " << lightCount << "\n";
+ for (int i = 0; i < lightCount; ++i)
+ s << " light" << i << " : " << GetLightType(i) << "\n";
+
+ s << " useUniformInsteadOfVertexColor = " << useUniformInsteadOfVertexColor << "\n";
+ s << " useVertexColorAsAmbientAndDiffuse = " << useVertexColorAsAmbientAndDiffuse << "\n";
+ s << " useVertexColorAsEmission = " << useVertexColorAsEmission << "\n";
+
+ s << " fogMode = " << fogMode << "\n";
+
+ for (int i = 0; i < texUnitCount; ++i)
+ {
+ s << " texture " << i << "\n";
+
+ s << " CUBE = " << ((texUnitCube[i])? "true": "false") << "\n";
+ s << " rgb combiner = " << CombinerToString(texUnitColorCombiner[i]) << "\n";
+ s << " alpba combiner = " << CombinerToString(texUnitAlphaCombiner[i]) << "\n";
+ s << " texGen = " << texUnitGen[i] << "\n";
+ s << " need matrix: " << (NeedTexUnitMatrix(i)?"true":"false") << "\n";
+ s << " need perspective divide: " << (IsTexUnitProjected(i)?"true":"false") << "\n";
+ }
+
+ s << " addSpecularafterTexturing = " << addSpecularAfterTexturing << "\n";
+ s << " alphaTest = " << alphaTest << "\n";
+
+ return s.str().c_str();
+}
diff --git a/Runtime/GfxDevice/opengles30/FixedFunctionStateGLES30.h b/Runtime/GfxDevice/opengles30/FixedFunctionStateGLES30.h
new file mode 100644
index 0000000..8de6be8
--- /dev/null
+++ b/Runtime/GfxDevice/opengles30/FixedFunctionStateGLES30.h
@@ -0,0 +1,65 @@
+#ifndef FIXEDFUNCTIONSTATE_GLES30_H
+#define FIXEDFUNCTIONSTATE_GLES30_H
+
+#include "IncludesGLES30.h"
+#include "Runtime/GfxDevice/GfxDeviceTypes.h"
+#include <string>
+
+// we can use one var to determine both shift and mask, but let help out the compiler ;-)
+#define FFPSTATE_SET_MASK(target, idx, val, shift_mul, mask) \
+do{ \
+ target &= ~(mask << ((idx)*shift_mul)); \
+ target |= (val << ((idx)*shift_mul)); \
+}while(0) \
+
+
+
+class FixedFunctionStateGLES30
+{
+public:
+ FixedFunctionStateGLES30();
+
+ UInt32 texUnitColorCombiner[kMaxSupportedTextureUnitsGLES];
+ UInt32 texUnitAlphaCombiner[kMaxSupportedTextureUnitsGLES];
+ bool texUnitCube[kMaxSupportedTextureUnitsGLES];
+ int texUnitGen[kMaxSupportedTextureUnitsGLES];
+ int texUnitCount;
+
+ // we will use 4bits per light - this way we can handle up to 8 lights (though kMaxEmulatedVertexLights = 4)
+ UInt32 lightType;
+ // we will use 2 bits per tex unit - one for perspective divide, the other one for if we need matrix mul at all
+ // this way we can store 16 texunit info (though kMaxSupportedTextureUnitsGLES = 8)
+ UInt32 texUnitMatrix;
+
+
+ bool lightingEnabled;
+ bool specularEnabled;
+ bool onlyDirectionalLights;
+ int lightCount : 8;
+ FogMode fogMode : 8;
+ CompareFunction alphaTest : 8;
+
+ bool useUniformInsteadOfVertexColor;
+ bool useVertexColorAsAmbientAndDiffuse;
+ bool useVertexColorAsEmission;
+ bool addSpecularAfterTexturing;
+
+ unsigned GetLightType(int i) const { return (lightType >> (i*4)) & 0xF; }
+ void SetLightType(int i, unsigned type) { FFPSTATE_SET_MASK(lightType, i, type, 4, 0xF); }
+
+ bool NeedTexUnitMatrix(int i) const { return ((texUnitMatrix >> (i*2)) & 0x1) != 0; }
+ bool IsTexUnitProjected(int i) const { return ((texUnitMatrix >> (i*2)) & 0x2) != 0; }
+
+ void SetTexUnitMatrixParam(int i, bool hasMatrix, bool isProjected)
+ {
+ int mask = (hasMatrix ? 1 : 0) | (isProjected ? 2 : 0);
+ FFPSTATE_SET_MASK(texUnitMatrix, i, mask, 2, 0x3);
+ }
+
+ std::string ToString () const;
+};
+
+
+#undef FFPSTATE_SET_MASK
+
+#endif /* FIXEDFUNCTIONSTATE_GLES30_H */
diff --git a/Runtime/GfxDevice/opengles30/GfxDeviceGLES30.cpp b/Runtime/GfxDevice/opengles30/GfxDeviceGLES30.cpp
new file mode 100644
index 0000000..240cb58
--- /dev/null
+++ b/Runtime/GfxDevice/opengles30/GfxDeviceGLES30.cpp
@@ -0,0 +1,3328 @@
+#include "UnityPrefix.h"
+#if GFX_SUPPORTS_OPENGLES30
+#include "Runtime/Graphics/ScreenManager.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+#include "Runtime/Shaders/GraphicsCaps.h"
+#include "Runtime/Shaders/MaterialProperties.h"
+#include "External/shaderlab/Library/texenv.h"
+#include "External/shaderlab/Library/TextureBinding.h"
+#include "Runtime/Misc/Allocator.h"
+#include "Runtime/Math/Matrix4x4.h"
+#include "Runtime/Math/Quaternion.h"
+#include "External/shaderlab/Library/shaderlab.h"
+#include "External/shaderlab/Library/properties.h"
+#include "External/shaderlab/Library/program.h"
+#include "Runtime/Graphics/RenderTexture.h"
+#include "Runtime/Graphics/Image.h"
+#include "Runtime/Threads/AtomicOps.h"
+#include "Runtime/GfxDevice/TransformState.h"
+#include "IncludesGLES30.h"
+#include "AssertGLES30.h"
+#include "ConstantBuffersGLES30.h"
+#include "ContextGLES30.h"
+#include "VBOGLES30.h"
+#include "TexturesGLES30.h"
+#include "CombinerGLES30.h"
+#include "GpuProgramsGLES30.h"
+#include "FixedFunctionStateGLES30.h"
+#include "ShaderGeneratorGLES30.h"
+#include "RenderTextureGLES30.h"
+#include "DebugGLES30.h"
+#include "TimerQueryGLES30.h"
+#include "TextureIdMapGLES30.h"
+#include "UtilsGLES30.h"
+#include "Runtime/GfxDevice/opengles30/TransformFeedbackSkinnedMesh.h"
+#include "DataBuffersGLES30.h"
+#include "Runtime/GfxDevice/opengles/ExtensionsGLES.h"
+
+#if UNITY_IPHONE
+ #include "PlatformDependent/iPhonePlayer/iPhoneSettings.h"
+#elif UNITY_ANDROID
+ #include "PlatformDependent/AndroidPlayer/ContextGLES.h"
+ #include "PlatformDependent/AndroidPlayer/EntryPoint.h"
+ #include "PlatformDependent/AndroidPlayer/AndroidSystemInfo.h"
+#endif
+
+#include "Runtime/GfxDevice/GLDataBufferCommon.h"
+
+#define UNITY_GLES3_ENTRYPOINTS_FROM_GETPROCADDR 0
+
+#if UNITY_ANDROID
+#undef UNITY_GLES3_ENTRYPOINTS_FROM_GETPROCADDR
+#define UNITY_GLES3_ENTRYPOINTS_FROM_GETPROCADDR 1
+#endif
+
+#if UNITY_GLES3_ENTRYPOINTS_FROM_GETPROCADDR
+
+#define STRINGIFY(x) STRINGIFY2(x)
+#define STRINGIFY2(x) #x
+
+#define DO_GLFUNC(retval, name, ...) typedef void (GL_APIENTRYP PFN##name) (__VA_ARGS__); \
+ retval GL_APIENTRY name (__VA_ARGS__) \
+{ \
+ static PFN##name addr = NULL; \
+ if(!addr) \
+ { \
+ /*DBG_LOG_GLES30("Retrieving GLES3 proc address " STRINGIFY(name) );*/ \
+ addr = (PFN##name) GetGLExtProcAddress(STRINGIFY(name)); \
+ if(!addr) \
+ { \
+ Assert("Could not find GLES 3.0 entry point" STRINGIFY(name)); \
+ return (retval)0; \
+ } \
+ DBG_LOG_GLES30("Success\n"); \
+ } \
+ __builtin_return(__builtin_apply((void (*)(...))addr, __builtin_apply_args(), 100)); \
+ /*DBG_LOG_GLES30("Called " STRINGIFY(name) " successfully\n");*/ \
+}
+
+DO_GLFUNC(void, glBindBufferBase, GLenum target, GLuint index, GLuint buffer)
+DO_GLFUNC(void, glBindBufferRange, GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr range)
+DO_GLFUNC(void, glBlitFramebuffer, GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter)
+DO_GLFUNC(void, glDeleteTransformFeedbacks, GLsizei n, const GLuint* ids)
+DO_GLFUNC(void, glDrawBuffers, GLsizei n, const GLenum* bufs)
+DO_GLFUNC(void, glGenQueries, GLsizei n, GLuint* ids)
+DO_GLFUNC(void, glGetActiveUniformBlockiv, GLuint program, GLuint uniformBlockIndex, GLenum pname, GLint* params)
+DO_GLFUNC(void, glGetActiveUniformBlockName, GLuint program, GLuint uniformBlockIndex, GLsizei bufSize, GLsizei *length, GLchar *uniformBlockName)
+DO_GLFUNC(void, glGetActiveUniformsiv, GLuint program, GLsizei uniformCount, const GLuint *uniformIndices, GLenum pname, GLint *params)
+DO_GLFUNC(void, glGetProgramBinary, GLuint program, GLsizei bufSize, GLsizei* length, GLenum* binaryFormat, GLvoid* binary)
+DO_GLFUNC(void, glGetQueryObjectuiv, GLuint id, GLenum pname, GLuint* params)
+DO_GLFUNC(GLuint, glGetUniformBlockIndex, GLuint program, const GLchar *uniformBlockName)
+DO_GLFUNC(void, glInvalidateFramebuffer, GLenum target, GLsizei numAttachments, const GLenum* attachments)
+DO_GLFUNC(void *, glMapBufferRange, GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access)
+DO_GLFUNC(void, glProgramBinary, GLuint program, GLenum binaryFormat, const GLvoid* binary, GLsizei length)
+DO_GLFUNC(void, glReadBuffer, GLenum buf)
+DO_GLFUNC(void, glRenderbufferStorageMultisample, GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height)
+DO_GLFUNC(void, glUniformBlockBinding, GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding)
+DO_GLFUNC(GLboolean, glUnmapBuffer, GLenum target)
+DO_GLFUNC(void, glGenTransformFeedbacks, GLsizei n, GLuint *ids)
+DO_GLFUNC(void, glVertexAttribIPointer, GLuint index, GLint size, GLenum type, GLsizei stride, const GLvoid* pointer)
+DO_GLFUNC(void, glTransformFeedbackVaryings, GLuint program, GLsizei count, const char* const* varyings, GLenum bufferMode)
+DO_GLFUNC(void, glBeginTransformFeedback, GLenum primitivemode)
+DO_GLFUNC(void, glEndTransformFeedback)
+DO_GLFUNC(void, glFlushMappedBufferRange, GLenum target, GLintptr offset, GLsizeiptr length)
+DO_GLFUNC(void, glGenVertexArrays, GLsizei n, GLuint* arrays)
+DO_GLFUNC(void, glDeleteVertexArrays, GLsizei n, const GLuint* arrays)
+DO_GLFUNC(void, glBindVertexArray, GLuint array)
+
+#undef DO_GLFUNC
+#undef STRINGIFY
+#undef STRINGIFY2
+
+#endif
+static void ClearFixedFunctionPrograms();
+
+// \todo [2013-04-16 pyry] UtilsGLES30 or similar for these?
+
+static int queryInt (UInt32 pname)
+{
+ int value = 0;
+ GLES_CHK(glGetIntegerv(pname, &value));
+ return value;
+}
+
+// let's play safe here:
+// ios/glesemu works just fine
+// and shadows demands more careful eps choice - do it later
+#define WORKAROUND_POLYGON_OFFSET UNITY_ANDROID
+
+// forward declarations
+
+namespace ShaderLab {
+ TexEnv* GetTexEnvForBinding( const TextureBinding& binding, const PropertySheet* props ); // pass.cpp
+}
+
+// local forward declarations
+struct DeviceStateGLES30;
+static void ApplyBackfaceMode( const DeviceStateGLES30& state );
+
+static FramebufferObjectManagerGLES30& GetFBOManager (DeviceStateGLES30& deviceState);
+
+// NOTE: GLES3.0 supports only 4 lights for now
+enum { kMaxSupportedVertexLightsByGLES30 = 4 };
+
+// Constant tables
+static const unsigned int kBlendModeES2[] = {
+ GL_ZERO, GL_ONE, GL_DST_COLOR, GL_SRC_COLOR, GL_ONE_MINUS_DST_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_COLOR,
+ GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA, GL_SRC_ALPHA_SATURATE, GL_ONE_MINUS_SRC_ALPHA,
+};
+
+static const unsigned int kBlendFuncES2[] = {
+ GL_FUNC_ADD, GL_FUNC_SUBTRACT,
+ GL_FUNC_REVERSE_SUBTRACT, GL_MIN, GL_MAX,
+};
+
+static const unsigned int kCmpFuncES2[] = {
+ GL_NEVER, GL_NEVER, GL_LESS, GL_EQUAL, GL_LEQUAL, GL_GREATER, GL_NOTEQUAL, GL_GEQUAL, GL_ALWAYS
+};
+
+static const unsigned int kStencilOpES2[] = {
+ GL_KEEP, GL_ZERO, GL_REPLACE, GL_INCR,
+ GL_DECR, GL_INVERT, GL_INCR_WRAP, GL_DECR_WRAP
+};
+
+static const GLenum kWrapModeES2[kTexWrapCount] = {
+ GL_REPEAT,
+ GL_CLAMP_TO_EDGE,
+};
+
+static const GLenum kGLES30TextureDimensionTable[kTexDimCount] = {0, -1/*GL_TEXTURE_1D*/, GL_TEXTURE_2D, GL_TEXTURE_3D, GL_TEXTURE_CUBE_MAP, 0};
+
+static const GLint kMinFilterES2[kTexFilterCount] = { GL_NEAREST_MIPMAP_NEAREST, GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR_MIPMAP_LINEAR };
+
+// --------------------------------------------------------------------------
+
+struct DeviceDepthStateGLES30 : public DeviceDepthState
+{
+ UInt32 depthFunc;
+};
+
+struct DeviceStencilStateGLES30 : public DeviceStencilState
+{
+ GLenum stencilFuncFront;
+ GLenum stencilFailOpFront;
+ GLenum depthFailOpFront;
+ GLenum depthPassOpFront;
+ GLenum stencilFuncBack;
+ GLenum stencilFailOpBack;
+ GLenum depthFailOpBack;
+ GLenum depthPassOpBack;
+};
+
+
+typedef std::map< GfxBlendState, DeviceBlendState, memcmp_less<GfxBlendState> > CachedBlendStates;
+typedef std::map< GfxDepthState, DeviceDepthStateGLES30, memcmp_less<GfxDepthState> > CachedDepthStates;
+typedef std::map< GfxStencilState, DeviceStencilStateGLES30, memcmp_less<GfxStencilState> > CachedStencilStates;
+typedef std::map< GfxRasterState, DeviceRasterState, memcmp_less<GfxRasterState> > CachedRasterStates;
+
+// --------------------------------------------------------------------------
+struct TextureUnitStateGLES3
+{
+ GLuint texID;
+ TextureDimension texDim;
+ unsigned int combColor, combAlpha;
+ Vector4f color;
+ TexGenMode texGen;
+ Matrix4x4f textureMatrix;
+ float bias;
+
+ // right let waste space here instead of device-level int
+ bool identityMatrix;
+ bool isProjected;
+ bool posForTexGen;
+ bool nrmForTexGen;
+
+ void Invalidate();
+
+ static bool PositionRequiredForTexGen(TexGenMode mode)
+ {
+ return (mode == kTexGenObject || mode == kTexGenEyeLinear);
+ }
+
+ static bool NormalRequiredForTexGen(TexGenMode mode)
+ {
+ return (mode == kTexGenSphereMap || mode == kTexGenCubeReflect || mode == kTexGenCubeNormal);
+ }
+
+ void SetTexGen( TexGenMode mode )
+ {
+ posForTexGen = PositionRequiredForTexGen(mode);
+ nrmForTexGen = NormalRequiredForTexGen(mode);
+
+ texGen = mode;
+ }
+};
+
+void TextureUnitStateGLES3::Invalidate()
+{
+ texID = -1;
+ texDim = kTexDimNone;
+ combColor = combAlpha = 0xFFFFFFFF;
+ color.Set( -1, -1, -1, -1 );
+ texGen = kTexGenUnknown;
+ posForTexGen = 0;
+ nrmForTexGen = 0;
+ textureMatrix.SetIdentity();
+ identityMatrix = true;
+ isProjected = false;
+ bias = 1.0e6f;
+}
+
+
+// --------------------------------------------------------------------------
+// TODO: optimize this. Right now we just send off whole 8 float3 UVs with each
+// immediate mode vertex. We could at least detect the number of them used from
+// ImmediateTexCoord calls.
+struct ImmediateVertexGLES30 {
+ Vector3f vertex;
+ Vector3f normal;
+ UInt32 color;
+ Vector3f texCoords[8];
+};
+
+struct ImmediateModeGLES30 {
+ std::vector<ImmediateVertexGLES30> m_Vertices;
+ ImmediateVertexGLES30 m_Current;
+ GfxPrimitiveType m_Mode;
+
+ DataBufferGLES30* m_IndexBufferQuads;
+
+ ImmediateModeGLES30();
+ ~ImmediateModeGLES30();
+ void Invalidate();
+};
+
+// --------------------------------------------------------------------------
+struct DeviceStateGLES30
+{
+ int m_TextureIDGenerator;
+
+ int depthFunc;
+ int depthWrite; // 0/1 or -1
+
+ int blending;
+ int srcBlend, destBlend, srcBlendAlpha, destBlendAlpha; // Blend modes
+ int blendOp, blendOpAlpha;
+ CompareFunction alphaTest;
+ float alphaValue;
+
+ CullMode culling;
+ bool appBackfaceMode, userBackfaceMode;
+ NormalizationMode
+ normalization;
+ int scissor;
+
+ bool lighting;
+ bool separateSpecular;
+ SimpleVec4 matDiffuse, matAmbient, matSpecular, matEmissive;
+ SimpleVec4 ambient;
+ float matShininess;
+ ColorMaterialMode
+ colorMaterial;
+
+ float offsetFactor, offsetUnits;
+
+ int colorWriteMask; // ColorWriteMask combinations
+ SimpleVec4 color;
+
+ TextureUnitStateGLES3
+ textures[kMaxSupportedTextureUnitsGLES];
+ int textureCount;
+ int activeTextureUnit;
+
+ // pure optimization: texGen is very special case and is used sparingly
+ UInt32 positionTexGen;
+ UInt32 normalTexGen;
+
+ int vertexLightCount;
+ LightType vertexLightTypes[kMaxSupportedVertexLights];
+
+ TransformState transformState;
+
+ ConstantBuffersGLES30
+ m_CBs;
+
+ DynamicVBO* m_DynamicVBO;
+ bool vboContainsColor;
+
+ int viewport[4];
+ int scissorRect[4];
+
+
+ GpuProgram* activeProgram;
+ const GpuProgramParameters* activeProgramParams;
+ dynamic_array<UInt8> activeProgramParamsBuffer;
+ UInt32 activeProgramID;
+
+
+ CachedBlendStates m_CachedBlendStates;
+ CachedDepthStates m_CachedDepthStates;
+ CachedStencilStates m_CachedStencilStates;
+ CachedRasterStates m_CachedRasterStates;
+
+ const DeviceBlendState* m_CurrBlendState;
+ const DeviceDepthStateGLES30* m_CurrDepthState;
+ const DeviceStencilStateGLES30* m_CurrStencilState;
+ int m_StencilRef;
+ const DeviceRasterState* m_CurrRasterState;
+
+ MaterialPropertyBlock m_MaterialProperties;
+ ImmediateModeGLES30 m_Imm;
+
+ // Framebuffer objects.
+ FramebufferObjectManagerGLES30* m_fboManager;
+ FramebufferObjectGLES30* m_activeFbo; //!< Currently bound FBO.
+ FramebufferObjectGLES30* m_defaultFbo; //!< Default render target FBO or null if using default framebuffer (0).
+
+public:
+ DeviceStateGLES30();
+ void Invalidate();
+ void ComputeFixedFunctionState(FixedFunctionStateGLES30& state, const GfxFogParams& fog) const;
+
+ inline void ApplyTexGen( UInt32 unit );
+ inline void DropTexGen( UInt32 unit );
+};
+
+DeviceStateGLES30::DeviceStateGLES30()
+: m_DynamicVBO(0)
+, m_fboManager(0)
+, m_activeFbo (0)
+, m_defaultFbo(0)
+{
+ m_TextureIDGenerator = 0;
+}
+
+void DeviceStateGLES30::Invalidate()
+{
+ DBG_LOG_GLES30("Invalidate");
+ int i;
+
+ depthFunc = -1; //unknown
+ depthWrite = -1;
+
+ blending = -1; // unknown
+ srcBlend = destBlend = -1; // won't match any GL mode
+ blendOp = blendOpAlpha = -1;
+ alphaTest = kFuncUnknown;
+ alphaValue = -1.0f;
+
+ culling = kCullUnknown;
+ normalization = kNormalizationUnknown;
+ scissor = -1;
+
+ lighting = false;
+ separateSpecular = false;
+
+ matDiffuse.set( -1, -1, -1, -1 );
+ matAmbient.set( -1, -1, -1, -1 );
+ matSpecular.set( -1, -1, -1, -1 );
+ matEmissive.set( -1, -1, -1, -1 );
+ ambient.set( -1, -1, -1, -1 );
+ matShininess = -1.0f;
+ colorMaterial = kColorMatUnknown;
+
+ offsetFactor = offsetUnits = -1000.0f;
+
+ colorWriteMask = -1; // TBD ?
+ m_StencilRef = -1;
+
+ color.set( -1, -1, -1, -1 );
+
+ activeTextureUnit = -1;
+ for( i = 0; i < kMaxSupportedTextureUnitsGLES; ++i )
+ textures[i].Invalidate();
+ textureCount = 0;
+
+ positionTexGen = 0;
+ normalTexGen = 0;
+
+ vertexLightCount = 0;
+ for ( i = 0; i < kMaxSupportedVertexLights; ++i)
+ vertexLightTypes[i] = kLightDirectional;
+
+ // make sure backface mode is in sync
+ appBackfaceMode = false;
+ userBackfaceMode = false;
+ ApplyBackfaceMode( *this );
+
+ vboContainsColor = true;
+
+ viewport[0] = 0;
+ viewport[1] = 0;
+ viewport[2] = 0;
+ viewport[3] = 0;
+
+ scissorRect[0] = 0;
+ scissorRect[1] = 0;
+ scissorRect[2] = 0;
+ scissorRect[3] = 0;
+
+ activeProgram = 0;
+ activeProgramParams = 0;
+ activeProgramParamsBuffer.resize_uninitialized(0);
+ activeProgramID = -1;
+
+ m_Imm.Invalidate();
+
+ m_activeFbo = 0;
+
+ InvalidateVertexInputCacheGLES30();
+
+ GLESAssert();
+}
+
+void DeviceStateGLES30::ComputeFixedFunctionState(FixedFunctionStateGLES30& state, const GfxFogParams& fog) const
+{
+ if (lighting)
+ {
+ int numLights = vertexLightCount;
+ bool onlyDir = true;
+ for(int i = 0 ; i < numLights ; ++i)
+ {
+ onlyDir = onlyDir && (vertexLightTypes[i] == kLightDirectional);
+ state.SetLightType(i,vertexLightTypes[i]);
+ }
+
+ state.lightingEnabled = true;
+ state.lightCount = numLights;
+ state.onlyDirectionalLights = onlyDir;
+ state.specularEnabled = separateSpecular;
+
+ switch (colorMaterial)
+ {
+ case kColorMatDisabled:
+ break;
+
+ case kColorMatAmbientAndDiffuse:
+ state.useVertexColorAsAmbientAndDiffuse = true;
+ break;
+
+ case kColorMatEmission:
+ state.useVertexColorAsEmission = true;
+ break;
+
+ default:
+ ErrorString("Unsupported color material mode");
+ break;
+ }
+ }
+ else
+ {
+ state.lightingEnabled = false;
+ state.lightCount = 0;
+ }
+
+ state.useUniformInsteadOfVertexColor = !vboContainsColor;
+ state.texUnitCount = textureCount;
+
+ for (int i = 0; i < textureCount; i++)
+ {
+ Assert(textures[i].texDim != kTexDimUnknown);
+ Assert(textures[i].texDim != kTexDimNone);
+ Assert(textures[i].texDim != kTexDimAny);
+ Assert(textures[i].texDim != kTexDim3D); // OpenGLES3.0 does NOT supports 3D textures
+ state.texUnitCube[i] = (textures[i].texDim == kTexDimCUBE);
+ state.texUnitColorCombiner[i] = textures[i].combColor,
+ state.texUnitAlphaCombiner[i] = textures[i].combAlpha;
+ state.texUnitGen[i] = textures[i].texGen;
+
+ bool needMatrix = !textures[i].identityMatrix || textures[i].texGen > kTexGenDisabled;
+ state.SetTexUnitMatrixParam(i, needMatrix, textures[i].isProjected);
+ }
+
+ state.fogMode = fog.mode;
+ switch (fog.mode)
+ {
+ case kFogUnknown:
+ case kFogDisabled:
+ state.fogMode = kFogDisabled;
+ default:
+ break;
+ }
+
+ if(gGraphicsCaps.gles30.hasAlphaTestQCOM)
+ {
+ // we dont want to generate special shader if we have alpha-test done gl style
+ state.alphaTest = kFuncDisabled;
+ }
+ else
+ {
+ state.alphaTest = alphaTest;
+ switch (alphaTest)
+ {
+ case kFuncNever:
+ /* \todo Disable drawing. */
+ break;
+ case kFuncUnknown:
+ case kFuncDisabled:
+ case kFuncAlways:
+ state.alphaTest = kFuncDisabled;
+ default:
+ break;
+ }
+ }
+}
+
+inline void DeviceStateGLES30::ApplyTexGen( UInt32 unit )
+{
+ const TextureUnitStateGLES3& state = textures[unit];
+
+ positionTexGen = state.posForTexGen ? positionTexGen | (1<<unit)
+ : positionTexGen & ~(1<<unit);
+
+ normalTexGen = state.nrmForTexGen ? normalTexGen | (1<<unit)
+ : normalTexGen & ~(1<<unit);
+}
+
+inline void DeviceStateGLES30::DropTexGen( UInt32 unit )
+{
+ positionTexGen &= ~(1<<unit);
+ normalTexGen &= ~(1<<unit);
+}
+
+
+#include "GfxDeviceGLES30.h"
+#include "Runtime/GfxDevice/GLESCommon.h"
+
+void GfxDeviceGLES30_MarkWorldViewProjDirty()
+{
+ GFX_GL_IMPL& device = static_cast<GFX_GL_IMPL&>(GetRealGfxDevice());
+ GetGLES30DeviceState(device).transformState.dirtyFlags |= TransformState::kWorldViewProjDirty;
+}
+
+void GfxDeviceGLES30_DisableDepthTest()
+{
+ GFX_GL_IMPL& device = static_cast<GFX_GL_IMPL&>(GetRealGfxDevice());
+ GetGLES30DeviceState(device).depthFunc = GL_NEVER;
+ GLES_CHK(glDisable(GL_DEPTH_TEST));
+}
+
+void GraphicsCaps::InitGLES30()
+{
+ // \todo [2013-04-16 pyry] Requires some serious cleanaup:
+ // - This functions shouldn't be member of GraphicsCaps (why it is?)
+ // - query extensions once, split to set and check from there
+ // - nicer wrapper for int caps queries
+
+ GLES_InitCommonCaps(this);
+ gGles3ExtFunc.InitExtFunc();
+
+ shaderCaps = kShaderLevel3;
+
+ maxLights = kMaxSupportedVertexLightsByGLES30; // vertex light count
+ hasAnisoFilter = QueryExtension("GL_EXT_texture_filter_anisotropic"); // has anisotropic filtering?
+ if (hasAnisoFilter)
+ GLES_CHK(glGetIntegerv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, (GLint *)&maxAnisoLevel));
+ else
+ maxAnisoLevel = 1;
+
+ maxTexImageUnits = 8;
+ GLES_CHK(glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &maxTexImageUnits));
+ maxTexImageUnits = std::max<int>(std::min<int>( maxTexImageUnits, kMaxSupportedTextureUnitsGLES ), 1);
+
+ maxTexUnits = maxTexImageUnits;
+ maxTexCoords = maxTexImageUnits;
+ maxMRTs = std::min<int>(FramebufferAttachmentsGLES30::kMaxColorAttachments, queryInt(GL_MAX_COLOR_ATTACHMENTS));
+
+ GLES_CHK(glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize));
+ GLES_CHK(glGetIntegerv(GL_MAX_CUBE_MAP_TEXTURE_SIZE, &maxCubeMapSize));
+ GLES_CHK(glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE, &maxRenderTextureSize));
+
+ hasMipLevelBias = true;
+ hasMipMaxLevel = true;
+
+ hasMultiSampleAutoResolve = false; // ES3 requires explicit glBlitFramebuffer() to do resolve. This affects how RenderTexture behaves.
+ hasMultiSample = true;
+
+ hasBlendSquare = true;
+ hasSeparateAlphaBlend = true;
+ hasBlendSub = true;
+ hasBlendMinMax = true;
+
+ hasS3TCCompression = false;
+
+ hasAutoMipMapGeneration = false; // \todo [2013-04-16 pyry] glGenMipmap() does exist
+
+ has3DTexture = false; // \todo [2013-04-16 pyry] Expose 3D textures
+
+ npot = kNPOTFull;
+ npotRT = npot;
+
+ hasRenderToTexture = true;
+ hasShadowCollectorPass = false;
+
+ hasHighPrecisionTextureCombiners = false; // \todo [2013-04-16 pyry] Yep. We can use highp (fp32) in FS. Should be enough.
+
+ hasRenderToCubemap = true;
+
+ // \note supportsTextureFormat[N < kTexFormatPCCount] = true
+
+ supportsTextureFormat[kTexFormatBGRA32] = QueryExtension("GL_APPLE_texture_format_BGRA8888");
+ supportsTextureFormat[kTexFormatDXT1] = QueryExtension("GL_EXT_texture_compression_s3tc") || QueryExtension("GL_EXT_texture_compression_dxt1");
+ supportsTextureFormat[kTexFormatDXT3] = QueryExtension("GL_EXT_texture_compression_s3tc") || QueryExtension("GL_CHROMIUM_texture_compression_dxt3");
+ supportsTextureFormat[kTexFormatDXT5] = QueryExtension("GL_EXT_texture_compression_s3tc") || QueryExtension("GL_CHROMIUM_texture_compression_dxt5");
+ supportsTextureFormat[kTexFormatPVRTC_RGB2] = QueryExtension("GL_IMG_texture_compression_pvrtc");
+ supportsTextureFormat[kTexFormatPVRTC_RGBA2] = QueryExtension("GL_IMG_texture_compression_pvrtc");
+ supportsTextureFormat[kTexFormatPVRTC_RGB4] = QueryExtension("GL_IMG_texture_compression_pvrtc");
+ supportsTextureFormat[kTexFormatPVRTC_RGBA4] = QueryExtension("GL_IMG_texture_compression_pvrtc");
+ supportsTextureFormat[kTexFormatATC_RGB4] = QueryExtension("GL_AMD_compressed_ATC_texture") || QueryExtension("GL_ATI_texture_compression_atitc");
+ supportsTextureFormat[kTexFormatATC_RGBA8] = QueryExtension("GL_AMD_compressed_ATC_texture") || QueryExtension("GL_ATI_texture_compression_atitc");
+
+ supportsTextureFormat[kTexFormatETC_RGB4] = true; // \note Mapped to ETC2.
+ supportsTextureFormat[kTexFormatEAC_R] = true;
+ supportsTextureFormat[kTexFormatEAC_R_SIGNED] = true;
+ supportsTextureFormat[kTexFormatEAC_RG] = true;
+ supportsTextureFormat[kTexFormatEAC_RG_SIGNED] = true;
+ supportsTextureFormat[kTexFormatETC2_RGB] = true;
+ supportsTextureFormat[kTexFormatETC2_RGBA1] = true;
+ supportsTextureFormat[kTexFormatETC2_RGBA8] = true;
+
+ const bool supportsASTC = QueryExtension("GL_KHR_texture_compression_astc_ldr");
+ for(int loop = kTexFormatASTC_RGB_4x4; loop <= kTexFormatASTC_RGBA_12x12; loop++)
+ supportsTextureFormat[loop] = supportsASTC;
+
+ // \todo [2013-04-16 pyry] Support means nothing until we can expose these to shaders as well.
+ // It requires changes to shader translator.
+ supportsRenderTextureFormat[kRTFormatARGB32] = true;
+ supportsRenderTextureFormat[kRTFormatDepth] = true;
+ supportsRenderTextureFormat[kRTFormatShadowMap] = true;
+ supportsRenderTextureFormat[kRTFormatRGB565] = true;
+ supportsRenderTextureFormat[kRTFormatARGB4444] = true;
+ supportsRenderTextureFormat[kRTFormatARGB1555] = true;
+ supportsRenderTextureFormat[kRTFormatDefault] = true;
+ supportsRenderTextureFormat[kRTFormatA2R10G10B10] = true;
+ supportsRenderTextureFormat[kRTFormatARGB64] = false;
+ supportsRenderTextureFormat[kRTFormatR8] = true;
+ supportsRenderTextureFormat[kRTFormatARGBInt] = true;
+ supportsRenderTextureFormat[kRTFormatRGInt] = true;
+ supportsRenderTextureFormat[kRTFormatRInt] = true;
+
+ if (QueryExtension("GL_EXT_color_buffer_float"))
+ {
+ // Support for fp render targets was stripped from spec as last minute decision. Sigh..
+ supportsRenderTextureFormat[kRTFormatARGBHalf] = true;
+ supportsRenderTextureFormat[kRTFormatRGHalf] = true;
+ supportsRenderTextureFormat[kRTFormatRHalf] = true;
+
+ supportsRenderTextureFormat[kRTFormatARGBFloat] = true;
+ supportsRenderTextureFormat[kRTFormatRGFloat] = true;
+ supportsRenderTextureFormat[kRTFormatRFloat] = true;
+
+ supportsRenderTextureFormat[kRTFormatDefaultHDR] = true; // ES3 backend uses R11F_G11F_B10F as default HDR format
+ }
+
+ const bool isPvrGpu = (rendererString.find("PowerVR") != string::npos);
+ const bool isMaliGpu = (rendererString.find("Mali") != string::npos);
+ const bool isAdrenoGpu = (rendererString.find("Adreno") != string::npos);
+ const bool isTegraGpu = (rendererString.find("Tegra") != string::npos);
+
+ hasNativeDepthTexture = true;
+ hasNativeShadowMap = true;
+
+ hasRenderTargetStencil = true;
+ hasTwoSidedStencil = true;
+ hasStencilInDepthTexture = true;
+ hasStencil = true;
+
+ has16BitFloatVertex = true;
+ needsToSwizzleVertexColors = false;
+
+ hasSRGBReadWrite = false; // \todo [2013-06-05 pyry] Doesn't function properly
+
+ disableSoftShadows = false;
+ buggyCameraRenderToCubemap = false;
+
+ hasShadowCollectorPass = false;
+
+ // Timer queries
+ gGraphicsCaps.hasTimerQuery = QueryExtension("GL_NV_timer_query");
+
+ // GLES3-specific variables.
+ gles30.maxAttributes = std::min<int>(kGLES3MaxVertexAttribs, queryInt(GL_MAX_VERTEX_ATTRIBS));
+ gles30.maxVaryings = queryInt(GL_MAX_VARYING_VECTORS);
+ gles30.maxSamples = queryInt(GL_MAX_SAMPLES);
+
+ hasTiledGPU = isPvrGpu || isAdrenoGpu || isMaliGpu;
+
+ const bool isAdreno330 = (rendererString.find("Adreno (TM) 330") != string::npos); // MSM8974 dev device, with rather unstable drivers.
+ const bool isMali628 = (rendererString.find("Mali-T628") != string::npos); // ARM TG4 dev device, mapbuffer broken.
+ const bool isGLESEmu = (rendererString.find("Mali OpenGL ES Emulator") != string::npos); // ARM OpenGL ES 3.0 emulator, glGetProgramBinary broken
+
+ gles30.useMapBuffer = true;
+ if(isAdrenoGpu || !isAdreno330)
+ gles30.useMapBuffer = false; // Buffer mapping is dead-slow on Adreno, but using BufferData() is broken on Adreno 330
+
+ if(isMali628)
+ gles30.useMapBuffer = false; // Mapbuffer broken on TG4
+
+ gles30.useProgramBinary = true; // Set true first, check actual support in Init call below.
+ gles30.useProgramBinary = GlslGpuProgramGLES30::InitBinaryShadersSupport();
+
+
+ gles30.useTFSkinning = true; // TF skinning used to be broken on adreno, worked around now.
+
+
+ // \todo [2013-04-17 pyry] Extension queries.
+
+ // \todo [2013-04-16 pyry] Figure out which gles20 flags make sense for ES3 as well.
+
+ // \todo [2013-04-16 pyry] Why init timer queries here?
+#if ENABLE_PROFILER
+ g_TimerQueriesGLES30.Init();
+#endif
+}
+
+GfxDevice* CreateGLES30GfxDevice()
+{
+#if UNITY_WIN || UNITY_LINUX || UNITY_BB10 || UNITY_ANDROID
+ InitializeGLES30();
+#endif
+
+ gGraphicsCaps.InitGLES30();
+
+#if UNITY_EDITOR
+ return NULL;
+#else
+ return UNITY_NEW_AS_ROOT(GFX_GL_IMPL(), kMemGfxDevice, "GLES30GfxDevice","");
+#endif
+}
+
+GFX_GL_IMPL::GFX_GL_IMPL()
+{
+ printf_console("Creating OpenGL ES 3.0 graphics device\n");
+ #if !GFX_DEVICE_VIRTUAL
+ impl = new GfxDeviceImpl();
+ #endif
+
+ OnCreate();
+ InvalidateState();
+ m_Renderer = kGfxRendererOpenGLES30;
+ m_IsThreadable = true;
+
+ m_UsesOpenGLTextureCoords = true;
+ m_UsesHalfTexelOffset = false;
+
+ STATE.m_CurrBlendState = NULL;
+ STATE.m_CurrDepthState = NULL;
+ STATE.m_CurrStencilState = NULL;
+ STATE.m_CurrRasterState = NULL;
+
+ extern RenderSurfaceBase* CreateBackBufferColorSurfaceGLES3();
+ SetBackBufferColorSurface(CreateBackBufferColorSurfaceGLES3());
+
+ extern RenderSurfaceBase* CreateBackBufferDepthSurfaceGLES3();
+ SetBackBufferDepthSurface(CreateBackBufferDepthSurfaceGLES3());
+}
+
+GFX_GL_IMPL::~GFX_GL_IMPL()
+{
+ TransformFeedbackSkinningInfo::CleanupTransformFeedbackShaders();
+ ClearFixedFunctionPrograms();
+ STATE.m_CBs.Clear();
+ delete STATE.m_DynamicVBO;
+ delete STATE.m_fboManager;
+
+ #if !GFX_DEVICE_VIRTUAL
+ delete impl;
+ #endif
+#if UNITY_WIN || UNITY_ANDROID
+ ShutdownGLES30();
+#endif
+}
+
+static void ActivateTextureUnitGLES3 (DeviceStateGLES30& state, int unit)
+{
+ if (state.activeTextureUnit == unit)
+ return;
+ GLES_CHK(glActiveTexture(GL_TEXTURE0 + unit));
+ state.activeTextureUnit = unit;
+}
+
+void GFX_GL_IMPL::InvalidateState()
+{
+ DBG_LOG_GLES30("InvalidateState");
+ m_FogParams.Invalidate();
+ STATE.transformState.Invalidate(m_BuiltinParamValues);
+ STATE.Invalidate();
+
+ m_BuiltinParamValues.SetVectorParam(kShaderVecFFMatEmission, Vector4f(1,1,1,1));
+ m_BuiltinParamValues.SetVectorParam(kShaderVecFFMatAmbient, Vector4f(1,1,1,1));
+ m_BuiltinParamValues.SetVectorParam(kShaderVecFFMatDiffuse, Vector4f(1,1,1,1));
+ m_BuiltinParamValues.SetVectorParam(kShaderVecFFMatSpecular, Vector4f(1,1,1,1));
+ m_BuiltinParamValues.SetVectorParam(kShaderVecFFMatShininess, Vector4f(1,1,1,1));
+
+ m_BuiltinParamValues.SetVectorParam(kShaderVecFFFogColor, Vector4f(0,0,0,0));
+ m_BuiltinParamValues.SetVectorParam(kShaderVecFFFogParams, Vector4f(0,0,0,0));
+
+ m_BuiltinParamValues.SetVectorParam(kShaderVecFFAlphaTestRef, Vector4f(0,0,0,0));
+
+ m_BuiltinParamValues.SetVectorParam(kShaderVecFFColor, Vector4f(1,1,1,1));
+ for (int i = 0; i < kMaxSupportedTextureUnitsGLES; i++)
+ m_BuiltinParamValues.SetVectorParam(BuiltinShaderVectorParam(kShaderVecFFTextureEnvColor0+i), Vector4f(0,0,0,0));
+}
+
+DeviceBlendState* GFX_GL_IMPL::CreateBlendState(const GfxBlendState& state)
+{
+ std::pair<CachedBlendStates::iterator, bool> result = STATE.m_CachedBlendStates.insert(std::make_pair(state, DeviceBlendState()));
+ if (!result.second)
+ return &result.first->second;
+
+ DeviceBlendState& glstate = result.first->second;
+ memcpy(&glstate.sourceState, &state, sizeof(glstate.sourceState));
+ DebugAssertIf(kFuncUnknown==state.alphaTest);
+
+ return &result.first->second;
+}
+
+DeviceDepthState* GFX_GL_IMPL::CreateDepthState(const GfxDepthState& state)
+{
+ std::pair<CachedDepthStates::iterator, bool> result = STATE.m_CachedDepthStates.insert(std::make_pair(state, DeviceDepthStateGLES30()));
+ if (!result.second)
+ return &result.first->second;
+
+ DeviceDepthStateGLES30& glstate = result.first->second;
+ memcpy(&glstate.sourceState, &state, sizeof(glstate.sourceState));
+ glstate.depthFunc = kCmpFuncES2[state.depthFunc];
+ return &result.first->second;
+}
+
+DeviceStencilState* GFX_GL_IMPL::CreateStencilState(const GfxStencilState& state)
+{
+ std::pair<CachedStencilStates::iterator, bool> result = STATE.m_CachedStencilStates.insert(std::make_pair(state, DeviceStencilStateGLES30()));
+ if (!result.second)
+ return &result.first->second;
+
+ DeviceStencilStateGLES30& st = result.first->second;
+ memcpy (&st.sourceState, &state, sizeof(st.sourceState));
+ st.stencilFuncFront = kCmpFuncES2[state.stencilFuncFront];
+ st.stencilFailOpFront = kStencilOpES2[state.stencilFailOpFront];
+ st.depthFailOpFront = kStencilOpES2[state.stencilZFailOpFront];
+ st.depthPassOpFront = kStencilOpES2[state.stencilPassOpFront];
+ st.stencilFuncBack = kCmpFuncES2[state.stencilFuncBack];
+ st.stencilFailOpBack = kStencilOpES2[state.stencilFailOpBack];
+ st.depthFailOpBack = kStencilOpES2[state.stencilZFailOpBack];
+ st.depthPassOpBack = kStencilOpES2[state.stencilPassOpBack];
+ return &result.first->second;
+}
+
+DeviceRasterState* GFX_GL_IMPL::CreateRasterState(const GfxRasterState& state)
+{
+ std::pair<CachedRasterStates::iterator, bool> result = STATE.m_CachedRasterStates.insert(std::make_pair(state, DeviceRasterState()));
+ if (!result.second)
+ return &result.first->second;
+
+ DeviceRasterState& glstate = result.first->second;
+ memcpy(&glstate.sourceState, &state, sizeof(glstate.sourceState));
+
+ return &result.first->second;
+}
+
+void GFX_GL_IMPL::SetBlendState(const DeviceBlendState* state, float alphaRef)
+{
+ DeviceBlendState* devstate = (DeviceBlendState*)state;
+
+ if (STATE.m_CurrBlendState == devstate && alphaRef == STATE.alphaValue)
+ return;
+
+ STATE.m_CurrBlendState = devstate;
+ if (!STATE.m_CurrBlendState)
+ return;
+
+ const GfxBlendState& desc = state->sourceState;
+ const GLenum glsrc = kBlendModeES2[desc.srcBlend];
+ const GLenum gldst = kBlendModeES2[desc.dstBlend];
+ const GLenum glsrca = kBlendModeES2[desc.srcBlendAlpha];
+ const GLenum gldsta = kBlendModeES2[desc.dstBlendAlpha];
+ const GLenum glfunc = kBlendFuncES2[desc.blendOp];
+ const GLenum glfunca = kBlendFuncES2[desc.blendOpAlpha];
+ const bool blendDisabled = (glsrc == GL_ONE && gldst == GL_ZERO && glsrca == GL_ONE && gldsta == GL_ZERO);
+
+ int mask = devstate->sourceState.renderTargetWriteMask;
+ if (STATE.m_activeFbo && STATE.m_activeFbo->GetNumColorAttachments() == 0)
+ mask = 0;
+
+ if( mask != STATE.colorWriteMask )
+ {
+ GLES_CHK(glColorMask( (mask & kColorWriteR) != 0, (mask & kColorWriteG) != 0, (mask & kColorWriteB) != 0, (mask & kColorWriteA) != 0 ));
+ STATE.colorWriteMask = mask;
+ }
+
+ if( blendDisabled )
+ {
+ if( STATE.blending != 0 )
+ {
+ GLES_CHK(glDisable (GL_BLEND));
+ STATE.blending = 0;
+ }
+ }
+ else
+ {
+ if( glsrc != STATE.srcBlend || gldst != STATE.destBlend || glsrca != STATE.srcBlendAlpha || gldsta != STATE.destBlendAlpha )
+ {
+ GLES_CHK(glBlendFuncSeparate(glsrc, gldst, glsrca, gldsta));
+ STATE.srcBlend = glsrc;
+ STATE.destBlend = gldst;
+ STATE.srcBlendAlpha = glsrca;
+ STATE.destBlendAlpha = gldsta;
+ }
+ if (glfunc != STATE.blendOp || glfunca != STATE.blendOpAlpha)
+ {
+ GLES_CHK(glBlendEquationSeparate(glfunc, glfunca));
+ STATE.blendOp = glfunc;
+ STATE.blendOpAlpha = glfunca;
+ }
+ if( STATE.blending != 1 )
+ {
+ GLES_CHK(glEnable( GL_BLEND ));
+ STATE.blending = 1;
+ }
+ }
+ // fragment shader is expected to implement per fragment culling
+ CompareFunction alphaTest = devstate->sourceState.alphaTest;
+
+ // \todo [2013-04-16 pyry] Alpha testing should be moved to shaders
+
+ if (gGraphicsCaps.gles30.hasAlphaTestQCOM && (alphaTest != STATE.alphaTest || alphaRef != STATE.alphaValue))
+ {
+ if (alphaTest != kFuncDisabled)
+ {
+ GLES_CHK(gGles3ExtFunc.glAlphaFuncQCOM(kCmpFuncES2[alphaTest], alphaRef));
+ GLES_CHK(glEnable(GL_ALPHA_TEST_QCOM));
+ }
+ else
+ {
+ GLES_CHK(glDisable(GL_ALPHA_TEST_QCOM));
+ }
+ }
+
+ STATE.alphaTest = alphaTest;
+ STATE.alphaValue = alphaRef;
+}
+
+void GFX_GL_IMPL::SetRasterState(const DeviceRasterState* state)
+{
+ DeviceRasterState* devstate = (DeviceRasterState*)state;
+ if(!devstate)
+ {
+ STATE.m_CurrRasterState = NULL;
+ return;
+ }
+
+ STATE.m_CurrRasterState = devstate;
+
+ CullMode cull = devstate->sourceState.cullMode;
+ if( cull != STATE.culling )
+ {
+ switch (cull) {
+ case kCullOff:
+ GLES_CHK(glDisable (GL_CULL_FACE));
+ break;
+ case kCullFront:
+ GLES_CHK(glCullFace (GL_FRONT));
+ GLES_CHK(glEnable (GL_CULL_FACE));
+ break;
+ case kCullBack:
+ GLES_CHK(glCullFace (GL_BACK));
+ GLES_CHK(glEnable (GL_CULL_FACE));
+ break;
+ default:
+ break;
+ }
+ STATE.culling = cull;
+ }
+
+ float zFactor = devstate->sourceState.slopeScaledDepthBias;
+ float zUnits = devstate->sourceState.depthBias;
+ if( zFactor != STATE.offsetFactor || zUnits != STATE.offsetUnits )
+ {
+
+#if WORKAROUND_POLYGON_OFFSET
+ // on some androids polygon offset work the other way (positive push to viewer)
+ // so we tweak projection matrix to do offset
+ // also use available depth precision better (on Adreno for example fix bugs with z-fighting)
+ // Game Programming Gems Vol1
+ // Eric Lengyel's "Tweaking a Vertex's Projected Depth Value"
+ // we use simplified formula: just smallest possible eps directly (multiplied by zUnits)
+ // we calculate eps for 16bit depth (good enough for larger depth)
+ // in projection matrix [2,2] = (f+n)/(n-f)
+ // so eps would be BitsMult * -1/proj[2,2]
+
+ static const float _BitsMult = -1.0f / (float)0xFFFF; // FFFF = 2^16-1, minus sign incorporated here
+
+ float* matrixElem = &m_BuiltinParamValues.GetWritableMatrixParam(kShaderMatProj).Get(2,2);
+
+ const float eps = _BitsMult / *matrixElem;
+ *matrixElem *= (1.0f + zUnits*eps);
+#else
+ GLES_CHK(glPolygonOffset( zFactor, zUnits ));
+ if( zFactor || zUnits )
+ GLES_CHK(glEnable (GL_POLYGON_OFFSET_FILL));
+ else
+ GLES_CHK(glDisable (GL_POLYGON_OFFSET_FILL));
+#endif
+ STATE.offsetFactor = zFactor;
+ STATE.offsetUnits = zUnits;
+ }
+}
+
+
+void GFX_GL_IMPL::SetDepthState(const DeviceDepthState* state)
+{
+ DeviceDepthStateGLES30* devstate = (DeviceDepthStateGLES30*)state;
+ if (STATE.m_CurrDepthState == devstate)
+ return;
+
+ STATE.m_CurrDepthState = devstate;
+
+ if (!STATE.m_CurrDepthState)
+ return;
+
+ const int depthFunc = devstate->depthFunc;
+ if( depthFunc != STATE.depthFunc )
+ {
+ if( depthFunc != GL_NEVER ) {
+ GLES_CHK(glDepthFunc (depthFunc));
+ GLES_CHK(glEnable (GL_DEPTH_TEST));
+ } else {
+ GLES_CHK(glDisable (GL_DEPTH_TEST));
+ }
+
+ STATE.depthFunc = depthFunc;
+ }
+
+ const int writeMode = devstate->sourceState.depthWrite ? GL_TRUE : GL_FALSE;
+ if( writeMode != STATE.depthWrite )
+ {
+ GLES_CHK(glDepthMask (writeMode));
+ STATE.depthWrite = writeMode;
+ }
+}
+
+void GFX_GL_IMPL::SetStencilState (const DeviceStencilState* state, int stencilRef)
+{
+ if (STATE.m_CurrStencilState == state && STATE.m_StencilRef == stencilRef)
+ return;
+ const DeviceStencilStateGLES30* st = static_cast<const DeviceStencilStateGLES30*>(state);
+ STATE.m_CurrStencilState = st;
+ if (!st)
+ return;
+
+ if (st->sourceState.stencilEnable)
+ GLES_CHK(glEnable (GL_STENCIL_TEST));
+ else
+ GLES_CHK(glDisable (GL_STENCIL_TEST));
+ if (gGraphicsCaps.hasTwoSidedStencil)
+ {
+ GLES_CHK(glStencilFuncSeparate (GL_FRONT, st->stencilFuncFront, stencilRef, st->sourceState.readMask));
+ GLES_CHK(glStencilOpSeparate (GL_FRONT, st->stencilFailOpFront, st->depthFailOpFront, st->depthPassOpFront));
+ GLES_CHK(glStencilFuncSeparate (GL_BACK, st->stencilFuncBack, stencilRef, st->sourceState.readMask));
+ GLES_CHK(glStencilOpSeparate (GL_BACK, st->stencilFailOpBack, st->depthFailOpBack, st->depthPassOpBack));
+ }
+ else
+ {
+ GLES_CHK(glStencilFunc (st->stencilFuncFront, stencilRef, st->sourceState.readMask));
+ GLES_CHK(glStencilOp (st->stencilFailOpFront, st->depthFailOpFront, st->depthPassOpFront));
+ }
+ GLES_CHK(glStencilMask (st->sourceState.writeMask));
+
+ STATE.m_StencilRef = stencilRef;
+}
+
+void GFX_GL_IMPL::SetSRGBWrite (bool enable)
+{
+ // \todo [2013-04-16 pyry] Implement sRGB support:
+ // - In ES3 sRGB bit is tied to format and there is no way to create views with different format
+ // - This is used rather liberally from Camera
+ // -> Use sRGB FBO for emulation, and defer necessary blits until it is known whether they are needed
+ Assert("Not implemented");
+}
+
+bool GFX_GL_IMPL::GetSRGBWrite ()
+{
+ return false;
+}
+
+void GFX_GL_IMPL::Clear (UInt32 clearFlags, const float color[4], float depth, int stencil)
+{
+ DBG_LOG_GLES30("Clear(%d, (%.2f, %.2f, %.2f, %.2f), %.2f, %d", clearFlags, color[0], color[1], color[2], color[3], depth, stencil);
+
+ // \todo [2013-04-16 pyry] Integer render targets require use of glClearBuffer()
+ // \todo [2013-04-29 pyry] Here was a call that restored FBO binding to default one. Why?
+
+ if (STATE.m_activeFbo && STATE.m_activeFbo->GetNumColorAttachments() == 0)
+ clearFlags &= ~kGfxClearColor;
+
+ // In OpenGL, clears are affected by color write mask and depth writing parameters.
+ // So make sure to set them!
+ GLbitfield flags = 0;
+ if (clearFlags & kGfxClearColor)
+ {
+ if (STATE.colorWriteMask != 15)
+ {
+ GLES_CHK(glColorMask( true, true, true, true ));
+ STATE.colorWriteMask = 15;
+ STATE.m_CurrBlendState = NULL;
+ }
+ flags |= GL_COLOR_BUFFER_BIT;
+ GLES_CHK(glClearColor( color[0], color[1], color[2], color[3] ));
+ }
+ if (clearFlags & kGfxClearDepth)
+ {
+ GLES_CHK(glDepthMask (GL_TRUE));
+ STATE.depthWrite = GL_TRUE;
+ STATE.m_CurrDepthState = NULL;
+ flags |= GL_DEPTH_BUFFER_BIT;
+ GLES_CHK(glClearDepthf( depth ));
+ }
+ if (clearFlags & kGfxClearStencil)
+ {
+ //@TODO: need to set stencil writes on?
+ flags |= GL_STENCIL_BUFFER_BIT;
+ GLES_CHK(glClearStencil (stencil));
+ }
+ GLES_CHK(glClear(flags));
+}
+
+static void ApplyBackfaceMode( const DeviceStateGLES30& state )
+{
+ DBG_LOG_GLES30("ApplyBackfaceMode");
+ if (state.appBackfaceMode != state.userBackfaceMode)
+ GLES_CHK(glFrontFace( GL_CCW ));
+ else
+ GLES_CHK(glFrontFace( GL_CW ));
+}
+
+void GFX_GL_IMPL::SetUserBackfaceMode( bool enable )
+{
+ DBG_LOG_GLES30("SetUserBackfaceMode(%s)", GetBoolString(enable));
+ if( STATE.userBackfaceMode == enable )
+ return;
+
+ STATE.userBackfaceMode = enable;
+ ApplyBackfaceMode( STATE );
+}
+
+void GFX_GL_IMPL::SetInvertProjectionMatrix( bool enable )
+{
+ Assert (!enable); // projection should never be flipped upside down on OpenGL
+}
+
+bool GFX_GL_IMPL::GetInvertProjectionMatrix() const
+{
+ return false;
+}
+
+void GFX_GL_IMPL::SetProjectionMatrix (const Matrix4x4f& matrix)
+{
+ DBG_LOG_GLES30("SetProjectionMatrix(...)");
+
+ CopyMatrix(matrix.GetPtr(), STATE.transformState.projectionMatrixOriginal.GetPtr());
+ CopyMatrix (matrix.GetPtr(), m_BuiltinParamValues.GetWritableMatrixParam(kShaderMatProj).GetPtr());
+ STATE.transformState.dirtyFlags |= TransformState::kProjDirty;
+}
+
+void GFX_GL_IMPL::SetWorldMatrix( const float matrix[16] )
+{
+ CopyMatrix( matrix, STATE.transformState.worldMatrix.GetPtr() );
+ STATE.transformState.dirtyFlags |= TransformState::kWorldDirty;
+}
+
+void GFX_GL_IMPL::SetViewMatrix( const float matrix[16] )
+{
+ STATE.transformState.SetViewMatrix (matrix, m_BuiltinParamValues);
+}
+
+void GFX_GL_IMPL::GetMatrix( float outMatrix[16] ) const
+{
+ STATE.transformState.UpdateWorldViewMatrix (m_BuiltinParamValues);
+ CopyMatrix (STATE.transformState.worldViewMatrix.GetPtr(), outMatrix);
+}
+
+const float* GFX_GL_IMPL::GetWorldMatrix() const
+{
+ return STATE.transformState.worldMatrix.GetPtr();
+}
+
+const float* GFX_GL_IMPL::GetViewMatrix() const
+{
+ return m_BuiltinParamValues.GetMatrixParam(kShaderMatView).GetPtr();
+}
+
+const float* GFX_GL_IMPL::GetProjectionMatrix() const
+{
+ return STATE.transformState.projectionMatrixOriginal.GetPtr();
+}
+
+const float* GFX_GL_IMPL::GetDeviceProjectionMatrix() const
+{
+ return GetProjectionMatrix();
+}
+
+void GFX_GL_IMPL::SetNormalizationBackface( NormalizationMode mode, bool backface )
+{
+ DBG_LOG_GLES30("SetNormalizationBackface(%d %s)", mode, backface?"back":"front");
+ STATE.normalization = mode;
+ if( STATE.appBackfaceMode != backface )
+ {
+ STATE.appBackfaceMode = backface;
+ ApplyBackfaceMode( STATE );
+ }
+}
+
+void GFX_GL_IMPL::SetFFLighting( bool on, bool separateSpecular, ColorMaterialMode colorMaterial )
+{
+ DBG_LOG_GLES30("SetFFLighting(%s, %s, %d)", on?"True":"False", separateSpecular?"True":"False", colorMaterial);
+ STATE.lighting = on;
+ STATE.separateSpecular = separateSpecular;
+ STATE.colorMaterial = colorMaterial;
+}
+
+void GFX_GL_IMPL::SetMaterial( const float ambient[4], const float diffuse[4], const float specular[4], const float emissive[4], const float shininess )
+{
+ DBG_LOG_GLES30("SetMaterial()");
+ STATE.matAmbient.set (ambient[0], ambient[1], ambient[2], 1.0F);
+ STATE.matDiffuse.set (diffuse);
+ STATE.matSpecular.set (specular[0], specular[1], specular[2], 1.0F);
+ STATE.matEmissive.set (emissive[0], emissive[1], emissive[2], 1.0F);
+ float glshine = std::max<float>(std::min<float>(shininess,1.0f), 0.0f) * 128.0f;
+ STATE.matShininess = glshine;
+}
+
+void GFX_GL_IMPL::SetColor( const float color[4] )
+{
+ DBG_LOG_GLES30("SetColor()");
+ STATE.color.set( color );
+ // Emulate OpenGL's behaviour
+ ImmediateColor( color[0], color[1], color[2], color[3] );
+}
+
+void GFX_GL_IMPL::SetViewport( int x, int y, int width, int height )
+{
+ DBG_LOG_GLES30("SetViewport(%d, %d, %d, %d)", x, y, width, height);
+ STATE.viewport[0] = x;
+ STATE.viewport[1] = y;
+ STATE.viewport[2] = width;
+ STATE.viewport[3] = height;
+ GLES_CHK(glViewport( x, y, width, height ));
+}
+
+void GFX_GL_IMPL::GetViewport( int* port ) const
+{
+ DBG_LOG_GLES30("GetViewport()");
+ port[0] = STATE.viewport[0];
+ port[1] = STATE.viewport[1];
+ port[2] = STATE.viewport[2];
+ port[3] = STATE.viewport[3];
+}
+
+void GFX_GL_IMPL::SetScissorRect( int x, int y, int width, int height )
+{
+ DBG_LOG_GLES30("SetScissorRect(%d, %d, %d, %d)", x, y, width, height);
+
+ if (STATE.scissor != 1)
+ {
+ GLES_CHK(glEnable( GL_SCISSOR_TEST ));
+ STATE.scissor = 1;
+ }
+
+ STATE.scissorRect[0] = x;
+ STATE.scissorRect[1] = y;
+ STATE.scissorRect[2] = width;
+ STATE.scissorRect[3] = height;
+ GLES_CHK(glScissor( x, y, width, height ));
+
+}
+
+void GFX_GL_IMPL::DisableScissor()
+{
+ DBG_LOG_GLES30("DisableScissor()");
+ if (STATE.scissor != 0)
+ {
+ GLES_CHK(glDisable( GL_SCISSOR_TEST ));
+ STATE.scissor = 0;
+ }
+}
+
+bool GFX_GL_IMPL::IsScissorEnabled() const
+{
+ DBG_LOG_GLES30("IsScissorEnabled():returns %s", STATE.scissor == 1?"True":"False");
+ return STATE.scissor == 1;
+}
+
+void GFX_GL_IMPL::GetScissorRect( int scissor[4] ) const
+{
+ DBG_LOG_GLES30("GetScissorRect()");
+ scissor[0] = STATE.scissorRect[0];
+ scissor[1] = STATE.scissorRect[1];
+ scissor[2] = STATE.scissorRect[2];
+ scissor[3] = STATE.scissorRect[3];
+}
+bool GFX_GL_IMPL::IsCombineModeSupported( unsigned int combiner )
+{
+ return true;
+}
+
+TextureCombinersHandle GFX_GL_IMPL::CreateTextureCombiners( int count, const ShaderLab::TextureBinding* texEnvs, const ShaderLab::PropertySheet* props, bool hasVertexColorOrLighting, bool usesAddSpecular )
+{
+ DBG_LOG_GLES30("CreateTextureCombiners()");
+ TextureCombinersGLES3* implGLES = TextureCombinersGLES3::Create (count, texEnvs);
+ return TextureCombinersHandle (implGLES);
+}
+
+void GFX_GL_IMPL::DeleteTextureCombiners( TextureCombinersHandle& textureCombiners )
+{
+ DBG_LOG_GLES30("DeleteTextureCombiners()");
+ TextureCombinersGLES3* implGLES = OBJECT_FROM_HANDLE(textureCombiners, TextureCombinersGLES3);
+ delete implGLES;
+ textureCombiners.Reset();
+}
+
+void GFX_GL_IMPL::SetTextureCombiners( TextureCombinersHandle textureCombiners, const ShaderLab::PropertySheet* props )
+{
+ DBG_LOG_GLES30("SetTextureCombiners()");
+ TextureCombinersGLES3* implGLES = OBJECT_FROM_HANDLE(textureCombiners,TextureCombinersGLES3);
+ Assert (implGLES);
+
+ const int count = std::min(gGraphicsCaps.maxTexUnits, implGLES->count);
+ // Fill in arrays
+ TexEnvData* texEnvData;
+ ALLOC_TEMP (texEnvData, TexEnvData, count);
+ for( int i = 0; i < count; ++i )
+ {
+ ShaderLab::TexEnv *te = ShaderLab::GetTexEnvForBinding( implGLES->texEnvs[i], props );
+ Assert( te != NULL );
+ te->PrepareData (implGLES->texEnvs[i].m_TextureName.index, implGLES->texEnvs[i].m_MatrixName, props, &texEnvData[i]);
+ }
+
+ Vector4f* texColors;
+ ALLOC_TEMP (texColors, Vector4f, implGLES->count);
+ for( int i = 0; i < implGLES->count; ++i )
+ {
+ const ShaderLab::TextureBinding& binding = implGLES->texEnvs[i];
+ texColors[i] = binding.GetTexColor().Get (props);
+ }
+ GFX_GL_IMPL::SetTextureCombinersThreadable(textureCombiners, texEnvData, texColors);
+
+}
+
+
+void GFX_GL_IMPL::SetTextureCombinersThreadable( TextureCombinersHandle textureCombiners, const TexEnvData* texEnvData, const Vector4f* texColors )
+{
+ DBG_LOG_GLES30("SetTextureCombiners()");
+ TextureCombinersGLES3* implGLES = OBJECT_FROM_HANDLE(textureCombiners,TextureCombinersGLES3);
+ Assert (implGLES);
+
+ const int count = std::min(gGraphicsCaps.maxTexUnits, implGLES->count);
+ STATE.textureCount = count;
+ for (int i = 0; i < count; ++i)
+ {
+ const ShaderLab::TextureBinding& binding = implGLES->texEnvs[i];
+
+ // set the texture
+ ApplyTexEnvData (i, i, texEnvData[i]);
+
+ // setup texture unit state
+ TextureUnitStateGLES3& texUnitState = STATE.textures[i];
+ texUnitState.color = texColors[i];
+ texUnitState.combColor = binding.m_CombColor;
+ texUnitState.combAlpha = binding.m_CombAlpha;
+ }
+ // we can just create mask and "and" with it
+ // but consider me lazy
+ for( int i = count ; i < gGraphicsCaps.maxTexUnits ; ++i)
+ STATE.DropTexGen(i);
+
+ STATE.activeProgram = 0;
+
+ // Get us back to TU 0, so we know where we stand
+ ActivateTextureUnitGLES3 (STATE, 0);
+
+}
+
+void GFX_GL_IMPL::SetTexture (ShaderType shaderType, int unit, int samplerUnit, TextureID texture, TextureDimension dim, float bias)
+{
+ DBG_LOG_GLES30("SetTexture(%d %d)", unit, texture.m_ID);
+ DebugAssertIf( unit < 0 || unit >= gGraphicsCaps.maxTexUnits );
+
+ GLenum texType = kGLES30TextureDimensionTable[dim];
+ GLuint targetTex = (GLuint)TextureIdMap::QueryNativeTexture(texture);
+
+ if (texType == (GLenum)-1)
+ {
+ Assert("Not supported");
+ return;
+ }
+
+ // \todo [2013-04-16 pyry] Shouldn't we clear state still?
+ if (targetTex == 0)
+ return;
+
+ TextureUnitStateGLES3& currTex = STATE.textures[unit];
+ if (STATE.textureCount > unit && targetTex == currTex.texID)
+ return; // Already bound.
+
+ ActivateTextureUnitGLES3 (STATE, unit);
+
+ GLES_CHK(glBindTexture(texType, targetTex));
+
+ if (STATE.textureCount <= unit)
+ STATE.textureCount = unit+1;
+ currTex.texID = targetTex;
+ currTex.texDim = dim;
+ if (currTex.texGen == kTexGenUnknown)
+ currTex.texGen = kTexGenDisabled;
+
+ STATE.ApplyTexGen(unit);
+
+ GLESAssert();
+
+ // \todo [2013-04-16 pyry] Lod bias is given from shader.
+ if (bias != std::numeric_limits<float>::infinity())
+ currTex.bias = bias;
+}
+
+void GFX_GL_IMPL::SetTextureTransform( int unit, TextureDimension dim, TexGenMode texGen, bool identity, const float matrix[16])
+{
+ DBG_LOG_GLES30("SetTextureTransform()");
+ DebugAssertIf( unit < 0 || unit >= gGraphicsCaps.maxTexUnits );
+ TextureUnitStateGLES3& unitState = STATE.textures[unit];
+
+ unitState.SetTexGen(texGen);
+ unitState.textureMatrix = *(Matrix4x4f const*)matrix;
+
+ // Since we will set texture matrix even if TexGen is disabled (since matrix can contain scale/offset information)
+ // we set textureMatrix to identity to be on a safe side
+ if (identity)
+ unitState.textureMatrix.SetIdentity();
+ unitState.identityMatrix = identity;
+
+ unitState.isProjected = false;
+ if (!identity && dim==kTexDim2D)
+ unitState.isProjected = (matrix[3] != 0.0f || matrix[7] != 0.0f || matrix[11] != 0.0f || matrix[15] != 1.0f);
+
+ STATE.ApplyTexGen(unit);
+}
+
+void GFX_GL_IMPL::SetTextureParams( TextureID texture, TextureDimension texDim, TextureFilterMode filter, TextureWrapMode wrap, int anisoLevel, bool hasMipMap, TextureColorSpace colorSpace )
+{
+ DBG_LOG_GLES30("SetTextureParams()");
+
+ TextureIdMapGLES30_QueryOrCreate(texture);
+
+ GLuint target = kGLES30TextureDimensionTable[texDim];
+ SetTexture (kShaderFragment, 0, 0, texture, texDim, std::numeric_limits<float>::infinity());
+
+ // Anisotropic texturing...
+ if( gGraphicsCaps.hasAnisoFilter )
+ {
+ anisoLevel = std::min( anisoLevel, gGraphicsCaps.maxAnisoLevel );
+ GLES_CHK(glTexParameteri( target, GL_TEXTURE_MAX_ANISOTROPY_EXT, anisoLevel ));
+ }
+
+ GLenum wrapMode = kWrapModeES2[wrap];
+ GLES_CHK(glTexParameteri( target, GL_TEXTURE_WRAP_S, wrapMode ));
+ GLES_CHK(glTexParameteri( target, GL_TEXTURE_WRAP_T, wrapMode ));
+
+ if( !hasMipMap && filter == kTexFilterTrilinear )
+ filter = kTexFilterBilinear;
+
+ GLES_CHK(glTexParameteri( target, GL_TEXTURE_MAG_FILTER, filter != kTexFilterNearest ? GL_LINEAR : GL_NEAREST ));
+ if( hasMipMap )
+ GLES_CHK(glTexParameteri( target, GL_TEXTURE_MIN_FILTER, kMinFilterES2[filter] ));
+ else
+ GLES_CHK(glTexParameteri (target, GL_TEXTURE_MIN_FILTER, filter != kTexFilterNearest ? GL_LINEAR : GL_NEAREST));
+
+ GLESAssert();
+}
+
+void GFX_GL_IMPL::SetMaterialProperties( const MaterialPropertyBlock& block )
+{
+ STATE.m_MaterialProperties = block;
+}
+
+void GFX_GL_IMPL::SetShadersThreadable (GpuProgram* programs[kShaderTypeCount], const GpuProgramParameters* params[kShaderTypeCount], UInt8 const * const paramsBuffer[kShaderTypeCount])
+{
+ GpuProgram* vertexProgram = programs[kShaderVertex];
+ GpuProgram* fragmentProgram = programs[kShaderFragment];
+
+ DBG_LOG_GLES30("SetShaders(%s, %s)",
+ GetShaderImplTypeString(vertexProgram? vertexProgram->GetImplType():kShaderImplUndefined),
+ GetShaderImplTypeString(fragmentProgram? fragmentProgram->GetImplType():kShaderImplUndefined));
+
+ // GLSL is only supported like this:
+ // vertex shader actually is both vertex & fragment linked program
+ // fragment shader is unused
+
+ if (vertexProgram && vertexProgram->GetImplType() == kShaderImplBoth)
+ {
+ Assert(fragmentProgram == 0 || fragmentProgram->GetImplType() == kShaderImplBoth);
+ STATE.activeProgram = vertexProgram;
+ STATE.activeProgramParams = params[kShaderVertex];
+ DebugAssert(STATE.activeProgramParams->IsReady());
+ int bufferSize = STATE.activeProgramParams->GetValuesSize();
+ STATE.activeProgramParamsBuffer.resize_uninitialized(bufferSize);
+ memcpy(STATE.activeProgramParamsBuffer.data(), paramsBuffer[kShaderVertex], bufferSize);
+ }
+ else
+ {
+ Assert(vertexProgram == 0);
+ STATE.activeProgram = 0;
+ STATE.activeProgramParams = 0;
+ STATE.activeProgramParamsBuffer.resize_uninitialized(0);
+ }
+}
+
+void GFX_GL_IMPL::CreateShaderParameters( ShaderLab::SubProgram* program, FogMode fogMode )
+{
+ GlslGpuProgramGLES30& programGLES = static_cast<GlslGpuProgramGLES30&>(program->GetGpuProgram());
+ programGLES.GetGLProgram(fogMode, program->GetParams(fogMode), program->GetChannels());
+}
+
+bool GFX_GL_IMPL::IsShaderActive( ShaderType type ) const
+{
+ //DBG_LOG_GLES30("IsShaderActive(%s): returns %s", GetShaderTypeString(type), STATE.shaderEnabledImpl[type] != kShaderImplUndefined?"True":"False");
+ //return STATE.shaderEnabledImpl[type] != kShaderImplUndefined;
+ DBG_LOG_GLES30("IsShaderActive(%s):", GetShaderTypeString(type));
+ return (STATE.activeProgram != 0);
+}
+
+void GFX_GL_IMPL::DestroySubProgram( ShaderLab::SubProgram* subprogram )
+{
+ //@TODO
+ //if (STATE.activeProgram == program)
+ //{
+ // STATE.activeProgram = NULL;
+ // STATE.activeProgramProps = NULL;
+ //}
+ delete subprogram;
+}
+
+void GFX_GL_IMPL::SetConstantBufferInfo( int id, int size )
+{
+ STATE.m_CBs.SetCBInfo(id, size);
+}
+
+void GFX_GL_IMPL::DisableLights( int startLight )
+{
+ DBG_LOG_GLES30("DisableLights(%d)", startLight);
+ startLight = std::min (startLight, gGraphicsCaps.maxLights);
+ STATE.vertexLightCount = startLight;
+ for (int i = startLight; i < kMaxSupportedVertexLightsByGLES30; ++i)
+ {
+ m_BuiltinParamValues.SetVectorParam(BuiltinShaderVectorParam(kShaderVecLight0Position + i), Vector4f(0.0f, 0.0f, 1.0f, 0.0f));
+ m_BuiltinParamValues.SetVectorParam(BuiltinShaderVectorParam(kShaderVecLight0Diffuse + i), Vector4f(0.0f, 0.0f, 0.0f, 0.0f));
+ }
+}
+
+void GFX_GL_IMPL::SetLight( int light, const GfxVertexLight& data)
+{
+ DBG_LOG_GLES30("SetLight(%d), [{%f, %f, %f, %f}, {%f, %f, %f, %f}, {%f, %f, %f, %f}, %f, %f, %f, %d]",
+ light,
+ data.position[0], data.position[1], data.position[2], data.position[3],
+ data.spotDirection[0], data.spotDirection[1], data.spotDirection[2], data.spotDirection[3],
+ data.color[0], data.color[1], data.color[2], data.color[3],
+ data.range, data.quadAtten, data.spotAngle, data.type);
+ Assert( light >= 0 && light < kMaxSupportedVertexLights );
+
+ if (light >= kMaxSupportedVertexLightsByGLES30)
+ return;
+
+ STATE.vertexLightTypes[light] = data.type;
+
+ // Transform lighting into view space
+ const Matrix4x4f& viewMat = m_BuiltinParamValues.GetMatrixParam(kShaderMatView);
+
+ // spot direction
+ Vector4f& spotDirection = m_BuiltinParamValues.GetWritableVectorParam(BuiltinShaderVectorParam(kShaderVecLight0SpotDirection + light));
+ if( data.spotAngle > 0.0f )
+ {
+ Vector3f d = viewMat.MultiplyVector3( (const Vector3f&)data.spotDirection );
+ spotDirection.Set(-d.x, -d.y, -d.z, 0.0f);
+ }
+ else
+ {
+ spotDirection.Set(0.0f, 0.0f, 1.0f, 0.0f);
+ }
+
+ Vector4f& position = m_BuiltinParamValues.GetWritableVectorParam(BuiltinShaderVectorParam(kShaderVecLight0Position + light));
+ if (data.type == kLightDirectional)
+ {
+ Vector3f p = viewMat.MultiplyVector3( (const Vector3f&)data.position );
+ position.Set(-p.x, -p.y, -p.z, 0.0f);
+ }
+ else
+ {
+ Vector3f p = viewMat.MultiplyPoint3( (const Vector3f&)data.position );
+ position.Set(p.x, p.y, p.z, 1.0f);
+ }
+
+ m_BuiltinParamValues.SetVectorParam(BuiltinShaderVectorParam(kShaderVecLight0Diffuse + light), data.color);
+ if (data.spotAngle > 0.0f)
+ {
+ // spot attenuation formula taken from D3D9 fixed-function emulation
+ // see: VertexPipeD3D9.cpp
+ const float cosTheta = cosf(Deg2Rad(data.spotAngle)*0.25f);
+ const float cosPhi = cosf(Deg2Rad(data.spotAngle)*0.5f);
+ const float cosDiff = cosTheta - cosPhi;
+ m_BuiltinParamValues.GetWritableVectorParam(BuiltinShaderVectorParam(kShaderVecLight0Atten + light)).Set(cosPhi, (cosDiff != 0.0f) ? 1.0f / cosDiff : 1.0f, data.quadAtten, data.range*data.range);
+ }
+ else
+ {
+ // non-spot light
+ m_BuiltinParamValues.GetWritableVectorParam(BuiltinShaderVectorParam(kShaderVecLight0Atten + light)).Set(-1.0f, 1.0f, data.quadAtten, data.range*data.range);
+ }
+}
+
+void GFX_GL_IMPL::SetAmbient( const float ambient[4] )
+{
+ DBG_LOG_GLES30("SetAmbient()");
+ STATE.ambient.set (ambient[0], ambient[1], ambient[2], ambient[3]);
+ m_BuiltinParamValues.SetVectorParam(kShaderVecLightModelAmbient, Vector4f(ambient));
+}
+
+void GFX_GL_IMPL::EnableFog (const GfxFogParams& fog)
+{
+ DBG_LOG_GLES30("EnableFog()");
+ DebugAssertIf( fog.mode <= kFogDisabled );
+ m_FogParams = fog;
+}
+
+void GFX_GL_IMPL::DisableFog()
+{
+ DBG_LOG_GLES30("DisableFog()");
+
+ if( m_FogParams.mode != kFogDisabled )
+ {
+ m_FogParams.mode = kFogDisabled;
+ m_FogParams.density = 0.0f;
+ }
+}
+
+VBO* GFX_GL_IMPL::CreateVBO()
+{
+ VBO* vbo = new GLES3VBO();
+ OnCreateVBO(vbo);
+ return vbo;
+}
+
+void GFX_GL_IMPL::DeleteVBO( VBO* vbo )
+{
+ OnDeleteVBO(vbo);
+ delete vbo;
+}
+
+DynamicVBO& GFX_GL_IMPL::GetDynamicVBO()
+{
+ if( !STATE.m_DynamicVBO ) {
+ STATE.m_DynamicVBO = new DynamicGLES3VBO();
+ }
+ return *STATE.m_DynamicVBO;
+}
+
+// ---------- render textures
+
+static FramebufferObjectManagerGLES30& GetFBOManager (DeviceStateGLES30& deviceState)
+{
+ if (!deviceState.m_fboManager)
+ deviceState.m_fboManager = new FramebufferObjectManagerGLES30();
+ return *deviceState.m_fboManager;
+}
+
+static RenderSurfaceGLES30* CreateRenderTexture (TextureDimension dim, TextureID texID, UInt32 internalFormat, int width, int height, int depth)
+{
+ if (dim == kTexDim2D)
+ {
+ Assert(depth == 1);
+ return new RenderTexture2DGLES30(texID, internalFormat, width, height);
+ }
+ else if (dim == kTexDimCUBE)
+ {
+ Assert(width == height && depth == 1);
+ return new RenderTextureCubeGLES30(texID, internalFormat, width, height);
+ }
+ else
+ {
+ Assert(!"Not supported");
+ return 0;
+ }
+}
+
+static RenderSurfaceGLES30* CreateShadowMapRenderTexture (TextureDimension dim, TextureID texID, UInt32 internalFormat, int width, int height, int depth)
+{
+ // \note Assumes that constructor binds texID!
+ if (dim == kTexDim2D)
+ {
+ Assert(depth == 1);
+ RenderTexture2DGLES30* tex = new RenderTexture2DGLES30(texID, internalFormat, width, height);
+ GLES_CHK(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE));
+ GLES_CHK(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL));
+ return tex;
+ }
+ else if (dim == kTexDimCUBE)
+ {
+ Assert(width == height && depth == 1);
+ RenderTextureCubeGLES30* tex = new RenderTextureCubeGLES30(texID, internalFormat, width, height);
+ GLES_CHK(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE));
+ GLES_CHK(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL));
+ return tex;
+ }
+ else
+ {
+ Assert(!"Not supported");
+ return 0;
+ }
+}
+
+static RenderSurfaceGLES30* CreateRenderBuffer (TextureDimension dim, UInt32 internalFormat, int width, int height, int depth, int numSamples)
+{
+ if (dim == kTexDim2D)
+ {
+ Assert(depth == 1);
+ return new RenderBufferGLES30(internalFormat, width, height, numSamples);
+ }
+ else if (dim == kTexDimCUBE)
+ {
+ Assert(width == height && depth == 1);
+ return new RenderBufferCubeGLES30(internalFormat, width, height, numSamples);
+ }
+ else
+ {
+ Assert(!"Not supported");
+ return 0;
+ }
+}
+
+RenderSurfaceHandle GFX_GL_IMPL::CreateRenderColorSurface (TextureID textureID, int width, int height, int samples, int depth, TextureDimension dim, RenderTextureFormat format, UInt32 createFlags)
+{
+ DBG_LOG_GLES30("CreateRenderColorSurface(id = %d, %dx%dx%d, samples = %d, dim = %d, fmt = %d, flags = 0x%04x)", textureID.m_ID, width, height, depth, samples, dim, format, createFlags);
+
+ // \note Sample count 0 and 1 both map to non-multisampled textures.
+
+ if (createFlags & kSurfaceCreateNeverUsed)
+ {
+ // Use dummy surface that is not backed by any real GL object.
+ return RenderSurfaceHandle(new DummyRenderSurfaceGLES30(width, height));
+ }
+ else
+ {
+ const bool isSRGB = (createFlags & kSurfaceCreateSRGB) != 0;
+ const UInt32 internalFormat = isSRGB ? GL_SRGB8_ALPHA8 : GetColorFormatGLES30(format);
+ const bool useRBO = textureID.m_ID == 0 || samples > 1;
+
+ Assert(internalFormat != GL_NONE);
+ Assert((createFlags & kSurfaceCreateShadowmap) == 0);
+
+ if (useRBO)
+ {
+ // \todo [2013-06-04 pyry] There is no global graphics caps for max samples.
+ const int numSamplesToUse = std::min(gGraphicsCaps.gles30.maxSamples, std::max(samples,1));
+ return RenderSurfaceHandle(CreateRenderBuffer(dim, internalFormat, width, height, depth, numSamplesToUse));
+ }
+ else
+ return RenderSurfaceHandle(CreateRenderTexture(dim, textureID, internalFormat, width, height, depth));
+ }
+
+ return RenderSurfaceHandle(0);
+}
+
+RenderSurfaceHandle GFX_GL_IMPL::CreateRenderDepthSurface (TextureID textureID, int width, int height, int samples, TextureDimension dim, DepthBufferFormat depthFormat, UInt32 createFlags)
+{
+ DBG_LOG_GLES30("CreateRenderDepthSurface(id = %d, %dx%d, samples = %d, dim = %d, fmt = %d, createFlags = 0x%04x)", textureID.m_ID, width, height, samples, dim, depthFormat, createFlags);
+
+ // \note Sample count 0 and 1 both map to non-multisampled textures.
+
+ if (depthFormat == kDepthFormatNone || (createFlags & kSurfaceCreateNeverUsed) != 0)
+ {
+ // Umm... Assuming that we don't want depth buffer at all, but still this is called?
+ return RenderSurfaceHandle(new DummyRenderSurfaceGLES30(width, height));
+ }
+ else
+ {
+ const bool shadowMap = (createFlags & kSurfaceCreateShadowmap) != 0;
+ const bool useStencil = !shadowMap;
+ const UInt32 internalFormat = useStencil ? GetDepthStencilFormatGLES30(depthFormat) : GetDepthOnlyFormatGLES30(depthFormat);
+ const bool useRBO = textureID.m_ID == 0 || samples > 1;
+
+ Assert(internalFormat != GL_NONE);
+ Assert(!shadowMap || !useRBO);
+
+ if (useRBO)
+ {
+ // \todo [2013-06-04 pyry] There is no global graphics caps for max samples.
+ const int numSamplesToUse = std::min(gGraphicsCaps.gles30.maxSamples, std::max(samples,1));
+ return RenderSurfaceHandle(CreateRenderBuffer(dim, internalFormat, width, height, 1, numSamplesToUse));
+ }
+ else if (shadowMap)
+ return RenderSurfaceHandle(CreateShadowMapRenderTexture(dim, textureID, internalFormat, width, height, 1));
+ else
+ return RenderSurfaceHandle(CreateRenderTexture(dim, textureID, internalFormat, width, height, 1));
+ }
+
+ return RenderSurfaceHandle(0);
+}
+
+void GFX_GL_IMPL::DestroyRenderSurface (RenderSurfaceHandle& rs)
+{
+ DBG_LOG_GLES30("DestroyRenderSurface(%p)", rs.object);
+
+ if (!rs.IsValid())
+ return;
+
+ RenderSurfaceGLES30* renderSurface = static_cast<RenderSurfaceGLES30*>(rs.object);
+
+ // Default FBO should not be managed from outside.
+ Assert(!STATE.m_defaultFbo || !IsInFramebufferAttachmentsGLES30(*STATE.m_defaultFbo->GetAttachments(), renderSurface));
+
+ // If rs was attached to current FBO, unbind it.
+ if (STATE.m_activeFbo && IsInFramebufferAttachmentsGLES30(*STATE.m_activeFbo->GetAttachments(), renderSurface))
+ {
+ if (STATE.m_defaultFbo)
+ BindFramebufferObjectGLES30(STATE.m_defaultFbo);
+ else
+ BindDefaultFramebufferGLES30();
+
+ STATE.m_activeFbo = STATE.m_defaultFbo;
+ }
+
+ // Delete FBOs where renderSurface was attached.
+ STATE.m_fboManager->InvalidateSurface(renderSurface);
+
+ delete renderSurface;
+ rs.object = 0;
+}
+
+static bool IsAnyRSHandleValid (const RenderSurfaceHandle* handles, int numHandles)
+{
+ for (int ndx = 0; ndx < numHandles; ndx++)
+ {
+ if (handles[ndx].IsValid())
+ return true;
+ }
+ return false;
+}
+
+void GFX_GL_IMPL::SetRenderTargets (int count, RenderSurfaceHandle* colorHandles, RenderSurfaceHandle depthHandle, int mipLevel, CubemapFace face)
+{
+ bool hasColor = !colorHandles[0].object->backBuffer;
+ bool hasDepth = !depthHandle.object->backBuffer;
+
+ DBG_LOG_GLES30("SetRenderTargets(count = %d, color = %s, depth = %s, mip = %d, face = %d)", count, (hasColor ? "yes" : "no"), (hasDepth ? "yes" : "no"), mipLevel, face);
+
+ // \todo [2013-05-06 pyry] Enable SetRenderTargets() variant with flags and check if we should discard buffers.
+ // \todo [2013-05-02 pyry] It probably makes sense to do RT change deferred.
+
+ Assert(count <= FramebufferAttachmentsGLES30::kMaxColorAttachments);
+
+ Assert(colorHandles[0].IsValid() && depthHandle.IsValid());
+ Assert(colorHandles[0].object->backBuffer == depthHandle.object->backBuffer);
+
+ if(!hasColor && !hasDepth)
+ {
+ // Assuming default FB - right?
+ if (STATE.m_activeFbo != STATE.m_defaultFbo)
+ {
+ if (STATE.m_defaultFbo)
+ BindFramebufferObjectGLES30(STATE.m_defaultFbo);
+ else
+ BindDefaultFramebufferGLES30();
+
+ STATE.m_activeFbo = STATE.m_defaultFbo;
+ GetRealGfxDevice().GetFrameStats().AddRenderTextureChange();
+ }
+ }
+ else
+ {
+ // Translate to FramebufferAttachments
+ FramebufferAttachmentsGLES30 attachments;
+
+ attachments.numColorAttachments = count;
+ attachments.depthStencil = static_cast<RenderSurfaceGLES30*>(depthHandle.object);
+ attachments.cubemapFace = face;
+
+ for (int ndx = 0; ndx < count; ndx++)
+ attachments.color[ndx] = static_cast<RenderSurfaceGLES30*>(colorHandles[ndx].object);
+
+ // Create (or fetch from cache) FBO
+ FramebufferObjectGLES30* fbo = GetFBOManager(STATE).GetFramebufferObject(attachments);
+
+ if (STATE.m_activeFbo != fbo)
+ {
+ BindFramebufferObjectGLES30(fbo);
+ STATE.m_activeFbo = fbo;
+ GetRealGfxDevice().GetFrameStats().AddRenderTextureChange();
+ }
+ }
+}
+
+void GFX_GL_IMPL::ResolveColorSurface (RenderSurfaceHandle srcHandle, RenderSurfaceHandle dstHandle)
+{
+ DBG_LOG_GLES30("ResolveColorSurface(src = %p, dst = %p)", srcHandle.object, dstHandle.object);
+
+ // Fetch temporary FBOs for resolve - use single color attachment.
+ FramebufferObjectGLES30* srcFbo = 0;
+ FramebufferObjectGLES30* dstFbo = 0;
+
+ Assert(srcHandle.object && dstHandle.object);
+
+ for (int ndx = 0; ndx < 2; ndx++)
+ {
+ FramebufferAttachmentsGLES30 attachments;
+ attachments.numColorAttachments = 1;
+ attachments.color[0] = static_cast<RenderSurfaceGLES30*>(ndx ? srcHandle.object : dstHandle.object);
+
+ (ndx ? srcFbo : dstFbo) = GetFBOManager(STATE).GetFramebufferObject(attachments);
+ }
+
+ Assert(srcFbo && dstFbo);
+
+ int width = static_cast<RenderSurfaceGLES30*>(dstHandle.object)->GetWidth();
+ int height = static_cast<RenderSurfaceGLES30*>(dstHandle.object)->GetHeight();
+
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, srcFbo->GetFboID());
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, dstFbo->GetFboID());
+ GLES_CHK(glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST));
+
+ // Restore old FBO binding
+ GLES_CHK(glBindFramebuffer(GL_FRAMEBUFFER, STATE.m_activeFbo ? STATE.m_activeFbo->GetFboID() : 0));
+}
+
+RenderSurfaceHandle GFX_GL_IMPL::GetActiveRenderColorSurface(int index)
+{
+ Assert(0 <= index && index <= FramebufferAttachmentsGLES30::kMaxColorAttachments);
+
+ if (STATE.m_activeFbo)
+ return RenderSurfaceHandle(STATE.m_activeFbo->GetColorAttachment(index));
+ else
+ return RenderSurfaceHandle(0);
+}
+
+RenderSurfaceHandle GFX_GL_IMPL::GetActiveRenderDepthSurface()
+{
+ if (STATE.m_activeFbo)
+ return RenderSurfaceHandle(STATE.m_activeFbo->GetDepthStencilAttachment());
+ else
+ return RenderSurfaceHandle(0);
+}
+
+void GFX_GL_IMPL::SetSurfaceFlags (RenderSurfaceHandle surf, UInt32 flags, UInt32 keepFlags)
+{
+ // \todo [2013-04-29 pyry] Implement handling for flags!
+}
+
+void GFX_GL_IMPL::DiscardContents (RenderSurfaceHandle& rs)
+{
+ DBG_LOG_GLES30("DiscardContents(%p)", rs.object);
+
+ if (!rs.IsValid())
+ return; // \todo [2013-06-05 pyry] Bug in threaded device code causes DiscardContents() calls to invalid handles.
+
+ FramebufferObjectGLES30* curFbo = STATE.m_activeFbo;
+ GLenum discardAttachments[FramebufferAttachmentsGLES30::kMaxColorAttachments+1];
+ int attachNdx = 0;
+
+ if (curFbo)
+ {
+ // Check if rs is attached to current fbo.
+ for (int colorNdx = 0; colorNdx < curFbo->GetNumColorAttachments(); colorNdx++)
+ {
+ if (rs.object == curFbo->GetColorAttachment(colorNdx))
+ discardAttachments[attachNdx++] = GL_COLOR_ATTACHMENT0+colorNdx;
+ }
+
+ if (rs.object == curFbo->GetDepthStencilAttachment())
+ {
+ // \todo [2013-05-02 pyry] Should we check if FBO actually has stencil attachment enabled?
+ discardAttachments[attachNdx++] = GL_DEPTH_STENCIL_ATTACHMENT;
+ }
+ }
+
+ Assert(attachNdx <= (int)(sizeof(discardAttachments)/sizeof(discardAttachments[0])));
+ if (attachNdx > 0)
+ GLES_CHK(glInvalidateFramebuffer(GL_FRAMEBUFFER, attachNdx, &discardAttachments[attachNdx]));
+
+ DBG_LOG_GLES30(" .. discarded in current FBO = %s", (attachNdx > 0) ? "true" : "false");
+
+ // If attachment was not discarded yet, do it later when it is bound.
+ if (attachNdx == 0)
+ {
+ RenderSurfaceGLES30* renderSurface = static_cast<RenderSurfaceGLES30*>(rs.object);
+ renderSurface->SetFlags(renderSurface->GetFlags() | RenderSurfaceGLES30::kDiscardOnBind);
+ }
+}
+
+// ---------- uploading textures
+
+void GFX_GL_IMPL::UploadTexture2D( TextureID texture, TextureDimension dimension, UInt8* srcData, int srcSize, int width, int height, TextureFormat format, int mipCount, UInt32 uploadFlags, int skipMipLevels, TextureUsageMode usageMode, TextureColorSpace colorSpace )
+{
+ ::UploadTexture2DGLES3( texture, dimension, srcData, width, height, format, mipCount, uploadFlags, skipMipLevels, colorSpace );
+}
+
+void GFX_GL_IMPL::UploadTextureSubData2D( TextureID texture, UInt8* srcData, int srcSize, int mipLevel, int x, int y, int width, int height, TextureFormat format, TextureColorSpace colorSpace )
+{
+ ::UploadTextureSubData2DGLES3( texture, srcData, mipLevel, x, y, width, height, format, colorSpace );
+}
+
+void GFX_GL_IMPL::UploadTextureCube( TextureID texture, UInt8* srcData, int srcSize, int faceDataSize, int size, TextureFormat format, int mipCount, UInt32 uploadFlags, TextureColorSpace colorSpace )
+{
+ ::UploadTextureCubeGLES3( texture, srcData, faceDataSize, size, format, mipCount, uploadFlags, colorSpace );
+}
+
+void GFX_GL_IMPL::UploadTexture3D( TextureID texture, UInt8* srcData, int srcSize, int width, int height, int depth, TextureFormat format, int mipCount, UInt32 uploadFlags )
+{
+ // \todo [2013-04-16 pyry] Add support.
+ ErrorString("3D textures are not supported by OpenGLES!");
+}
+
+void GFX_GL_IMPL::DeleteTexture(TextureID texture)
+{
+ GLuint targetTex = (GLuint)TextureIdMap::QueryNativeTexture(texture);
+ if(targetTex == 0)
+ return;
+
+ REGISTER_EXTERNAL_GFX_DEALLOCATION(texture.m_ID);
+ GLES_CHK(glDeleteTextures(1, &targetTex));
+
+ // invalidate texture unit states that used this texture
+ for( int i = 0; i < gGraphicsCaps.maxTexUnits; ++i )
+ {
+ TextureUnitStateGLES3& currTex = STATE.textures[i];
+ if( currTex.texID == targetTex )
+ currTex.Invalidate();
+ }
+
+ TextureIdMap::RemoveTexture(texture);
+}
+
+// ---------- context
+
+GfxDevice::PresentMode GFX_GL_IMPL::GetPresentMode()
+{
+ return UNITY_IPHONE ? kPresentAfterDraw : kPresentBeforeUpdate;
+}
+
+static void ResetFboBindingToDefault (DeviceStateGLES30& state)
+{
+ if (state.m_activeFbo != state.m_defaultFbo)
+ {
+ if (state.m_defaultFbo)
+ BindFramebufferObjectGLES30(state.m_defaultFbo);
+ else
+ BindDefaultFramebufferGLES30();
+ state.m_activeFbo = state.m_defaultFbo;
+ }
+}
+
+void GFX_GL_IMPL::BeginFrame()
+{
+ DBG_LOG_GLES30("BeginFrame()");
+ m_InsideFrame = true;
+
+ if (gGraphicsCaps.hasTiledGPU)
+ {
+ // \todo [2013-05-02 pyry] Should we reset FBO binding here?
+ ResetFboBindingToDefault(STATE);
+ if(STATE.scissor)
+ GLES_CHK(glDisable(GL_SCISSOR_TEST));
+ GLES_CHK(glViewport(0, 0, 65536, 65536));
+ GLES_CHK(glClearColor(0.0f, 0.0f, 0.0f, 1.0f));
+ GLES_CHK(glClearStencil(0));
+ GLES_CHK(glClearDepthf(1.0f));
+ GLES_CHK(glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT));
+ GLES_CHK(glViewport(STATE.viewport[0], STATE.viewport[1], STATE.viewport[2], STATE.viewport[3]));
+ if(STATE.scissor)
+ GLES_CHK(glEnable(GL_SCISSOR_TEST));
+
+ }
+}
+void GFX_GL_IMPL::EndFrame()
+{
+ // \todo [2013-05-02 pyry] Do we really want to reset FBO binding here?
+ ResetFboBindingToDefault(STATE);
+ if (STATE.m_activeFbo != 0)
+ {
+ // If rendering to FBO, discard contents.
+ static const GLenum attachments[] = { GL_COLOR_ATTACHMENT0, GL_DEPTH_STENCIL_ATTACHMENT };
+ GLES_CHK(glInvalidateFramebuffer(GL_FRAMEBUFFER, sizeof(attachments)/sizeof(attachments[0]), &attachments[0]));
+ }
+ else
+ {
+ // System "FBO", discard only depthstencil
+ static const GLenum attachments[] = { GL_DEPTH, GL_STENCIL };
+ GLES_CHK(glInvalidateFramebuffer(GL_FRAMEBUFFER, sizeof(attachments)/sizeof(attachments[0]), &attachments[0]));
+ }
+
+ GetBufferManagerGLES30()->AdvanceFrame();
+
+ DBG_LOG_GLES30("EndFrame()");
+ m_InsideFrame = false;
+}
+
+bool GFX_GL_IMPL::IsValidState()
+{
+#if UNITY_ANDROID
+ return ContextGLES::IsValid();
+#else
+ return true;
+#endif
+}
+
+bool GFX_GL_IMPL::HandleInvalidState()
+{
+#if UNITY_ANDROID
+ bool needReload;
+ if (!ContextGLES::HandleInvalidState(&needReload))
+ return false;
+ if (needReload)
+ ReloadResources();
+ return true;
+#else
+ return true;
+#endif
+}
+
+void GFX_GL_IMPL::PresentFrame()
+{
+ DBG_LOG_GLES30("====================================");
+ DBG_LOG_GLES30("====================================");
+ DBG_LOG_GLES30("PresentFrame");
+ DBG_LOG_GLES30("====================================");
+ DBG_LOG_GLES30("====================================");
+
+#if UNITY_WIN
+ PresentContextGLES30();
+#else
+ PresentContextGLES();
+#endif
+}
+
+void GFX_GL_IMPL::FinishRendering()
+{
+ GLES_CHK(glFinish());
+}
+
+// ---------- immediate mode rendering
+
+// we break very large immediate mode submissions into multiple batches internally
+const int kMaxImmediateVerticesPerDraw = 2048;
+
+
+ImmediateModeGLES30::ImmediateModeGLES30()
+ : m_Mode (kPrimitiveTriangles)
+ , m_IndexBufferQuads(0)
+{
+#if 0
+ m_QuadsIB = (UInt16*)UNITY_MALLOC(kMemGeometry, kMaxImmediateVerticesPerDraw*6*sizeof(UInt16));
+ UInt32 baseIndex = 0;
+ UInt16* ibPtr = m_QuadsIB;
+ for( int i = 0; i < kMaxImmediateVerticesPerDraw; ++i )
+ {
+ ibPtr[0] = baseIndex + 1;
+ ibPtr[1] = baseIndex + 2;
+ ibPtr[2] = baseIndex;
+ ibPtr[3] = baseIndex + 2;
+ ibPtr[4] = baseIndex + 3;
+ ibPtr[5] = baseIndex;
+ baseIndex += 4;
+ ibPtr += 6;
+ }
+#endif
+}
+
+ImmediateModeGLES30::~ImmediateModeGLES30()
+{
+ Invalidate();
+}
+
+void ImmediateModeGLES30::Invalidate()
+{
+ if (m_IndexBufferQuads)
+ {
+ m_IndexBufferQuads->Release();
+ m_IndexBufferQuads = 0;
+ }
+
+ m_Vertices.clear();
+ memset(&m_Current, 0, sizeof(m_Current));
+}
+
+void GFX_GL_IMPL::ImmediateVertex( float x, float y, float z )
+{
+ // If the current batch is becoming too large, internally end it and begin it again.
+ size_t currentSize = STATE.m_Imm.m_Vertices.size();
+ if( currentSize >= kMaxImmediateVerticesPerDraw - 4 )
+ {
+ GfxPrimitiveType mode = STATE.m_Imm.m_Mode;
+ // For triangles, break batch when multiple of 3's is reached.
+ if( mode == kPrimitiveTriangles && currentSize % 3 == 0 )
+ {
+ ImmediateEnd();
+ ImmediateBegin( mode );
+ }
+ // For other primitives, break on multiple of 4's.
+ // NOTE: This won't quite work for triangle strips, but we'll just pretend
+ // that will never happen.
+ else if( mode != kPrimitiveTriangles && currentSize % 4 == 0 )
+ {
+ ImmediateEnd();
+ ImmediateBegin( mode );
+ }
+ }
+ Vector3f& vert = STATE.m_Imm.m_Current.vertex;
+ vert.x = x;
+ vert.y = y;
+ vert.z = z;
+ STATE.m_Imm.m_Vertices.push_back( STATE.m_Imm.m_Current );
+}
+
+void GFX_GL_IMPL::ImmediateNormal( float x, float y, float z )
+{
+ STATE.m_Imm.m_Current.normal.x = x;
+ STATE.m_Imm.m_Current.normal.y = y;
+ STATE.m_Imm.m_Current.normal.z = z;
+}
+
+void GFX_GL_IMPL::ImmediateColor( float r, float g, float b, float a )
+{
+ r = clamp01(r);
+ g = clamp01(g);
+ b = clamp01(b);
+ a = clamp01(a);
+
+ STATE.m_Imm.m_Current.color =
+ ((UInt32)(a * 255.0f) << 24) |
+ ((UInt32)(b * 255.0f) << 16) |
+ ((UInt32)(g * 255.0f) << 8) |
+ ((UInt32)(r * 255.0f));
+}
+
+void GFX_GL_IMPL::ImmediateTexCoordAll( float x, float y, float z )
+{
+ for( int i = 0; i < gGraphicsCaps.maxTexCoords; ++i )
+ {
+ Vector3f& uv = STATE.m_Imm.m_Current.texCoords[i];
+ uv.x = x;
+ uv.y = y;
+ uv.z = z;
+ }
+}
+
+void GFX_GL_IMPL::ImmediateTexCoord( int unit, float x, float y, float z )
+{
+ if( unit < 0 || unit >= 8 )
+ {
+ ErrorString( "Invalid unit for texcoord" );
+ return;
+ }
+ Vector3f& uv = STATE.m_Imm.m_Current.texCoords[unit];
+ uv.x = x;
+ uv.y = y;
+ uv.z = z;
+}
+
+void GFX_GL_IMPL::ImmediateBegin( GfxPrimitiveType type )
+{
+ STATE.m_Imm.m_Mode = type;
+ STATE.m_Imm.m_Vertices.clear();
+}
+
+void GFX_GL_IMPL::ImmediateEnd()
+{
+ if (STATE.m_Imm.m_Vertices.empty())
+ return;
+
+ const UInt32 minBufferThreshold = 2048;
+
+ const int numVertices = STATE.m_Imm.m_Vertices.size();
+ const UInt32 vertexBufStride = (UInt32)sizeof(ImmediateVertexGLES30);
+ const UInt32 vertexBufUsage = GL_STREAM_DRAW;
+ const UInt32 vertexBufSize = vertexBufStride*numVertices;
+ const bool useBuffer = vertexBufSize >= minBufferThreshold;
+ DataBufferGLES30* vertexBuffer = useBuffer ? GetBufferManagerGLES30()->AcquireBuffer(numVertices*vertexBufStride, vertexBufUsage) : 0;
+ VertexArrayInfoGLES30 vertexSetup;
+
+ // \todo [2013-05-31 pyry] Recreate or update?
+ if (vertexBuffer)
+ vertexBuffer->RecreateWithData(vertexBufSize, vertexBufUsage, &STATE.m_Imm.m_Vertices[0]);
+
+ if (STATE.m_Imm.m_Mode == kPrimitiveQuads && !STATE.m_Imm.m_IndexBufferQuads)
+ {
+ // \todo [2013-05-31 pyry] Move somewhere else.
+ const int quadCount = kMaxImmediateVerticesPerDraw/4;
+ const int quadIndexCount = quadCount * 6;
+ const int indexBufferSize = quadIndexCount * sizeof(UInt16);
+ const UInt32 indexBufferUsage = GL_STATIC_DRAW;
+ std::vector<UInt16> quadIndices (quadIndexCount);
+
+ for (int quadNdx = 0; quadNdx < quadCount; ++quadNdx)
+ {
+ const UInt16 srcBaseNdx = quadNdx*4;
+ const int dstBaseNdx = quadNdx*6;
+ quadIndices[dstBaseNdx + 0] = srcBaseNdx + 1;
+ quadIndices[dstBaseNdx + 1] = srcBaseNdx + 2;
+ quadIndices[dstBaseNdx + 2] = srcBaseNdx;
+ quadIndices[dstBaseNdx + 3] = srcBaseNdx + 2;
+ quadIndices[dstBaseNdx + 4] = srcBaseNdx + 3;
+ quadIndices[dstBaseNdx + 5] = srcBaseNdx;
+ }
+
+ STATE.m_Imm.m_IndexBufferQuads = GetBufferManagerGLES30()->AcquireBuffer(indexBufferSize, indexBufferUsage);
+ STATE.m_Imm.m_IndexBufferQuads->RecreateWithData(indexBufferSize, indexBufferUsage, &quadIndices[0]);
+ }
+
+ // Fill in vertex setup info.
+ {
+ const UInt8* basePtr = vertexBuffer ? 0 : (const UInt8*)&STATE.m_Imm.m_Vertices[0];
+ const UInt32 buffer = vertexBuffer ? vertexBuffer->GetBuffer() : 0;
+
+ // \todo [2013-05-31 pyry] Do not send unused attributes!
+ vertexSetup.enabledArrays = (1<<kGLES3AttribLocationPosition)
+ | (1<<kGLES3AttribLocationColor)
+ | (1<<kGLES3AttribLocationNormal);
+
+ // All are sourcing from same buffer
+ for (int i = 0; i < kGLES3MaxVertexAttribs; i++)
+ vertexSetup.buffers[i] = buffer;
+
+ vertexSetup.arrays[kGLES3AttribLocationPosition] = VertexInputInfoGLES30(basePtr + 0, kChannelFormatFloat, 3, vertexBufStride);
+ vertexSetup.arrays[kGLES3AttribLocationNormal] = VertexInputInfoGLES30(basePtr + 3*sizeof(float), kChannelFormatFloat, 3, vertexBufStride);
+ vertexSetup.arrays[kGLES3AttribLocationColor] = VertexInputInfoGLES30(basePtr + 6*sizeof(float), kChannelFormatColor, 4, vertexBufStride);
+ UInt32 curOffset = 6*sizeof(float) + sizeof(UInt32);
+
+ for (int texCoordNdx = 0; texCoordNdx < (kGLES3MaxVertexAttribs-kGLES3AttribLocationTexCoord0); texCoordNdx++)
+ {
+ const int attribLoc = kGLES3AttribLocationTexCoord0+texCoordNdx;
+
+ if (attribLoc < gGraphicsCaps.gles30.maxAttributes)
+ {
+ vertexSetup.enabledArrays |= (1<<attribLoc);
+ vertexSetup.arrays[kGLES3AttribLocationTexCoord0+texCoordNdx] =
+ VertexInputInfoGLES30(basePtr + curOffset, kChannelFormatFloat, 3, vertexBufStride);
+ curOffset += 3*sizeof(float);
+ }
+ }
+ }
+
+ // Setup state
+ BeforeDrawCall(true /* immediate */);
+ SetupDefaultVertexArrayStateGLES30(vertexSetup);
+
+ switch (STATE.m_Imm.m_Mode)
+ {
+ case kPrimitiveTriangles:
+ GLES_CHK(glDrawArrays(GL_TRIANGLES, 0, numVertices));
+ m_Stats.AddDrawCall(numVertices / 3, numVertices);
+ break;
+
+ case kPrimitiveTriangleStripDeprecated:
+ GLES_CHK(glDrawArrays(GL_TRIANGLE_STRIP, 0, numVertices));
+ m_Stats.AddDrawCall(numVertices - 2, numVertices);
+ break;
+
+ case kPrimitiveLines:
+ GLES_CHK(glDrawArrays(GL_LINES, 0, numVertices));
+ m_Stats.AddDrawCall(numVertices / 2, numVertices);
+ break;
+
+ case kPrimitiveQuads:
+ Assert(STATE.m_Imm.m_IndexBufferQuads);
+ GLES_CHK(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, STATE.m_Imm.m_IndexBufferQuads->GetBuffer()));
+ GLES_CHK(glDrawElements(GL_TRIANGLES, (numVertices/4)*6, GL_UNSIGNED_SHORT, 0));
+ GLES_CHK(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
+ m_Stats.AddDrawCall(numVertices / 2, numVertices);
+ break;
+
+ default:
+ AssertString("ImmediateEnd: unknown draw mode");
+ }
+
+ if (vertexBuffer)
+ vertexBuffer->Release();
+}
+
+// ---------- readback path
+
+static int GetFirstValidColorAttachmentNdx (const FramebufferObjectGLES30* fbo)
+{
+ for (int colorNdx = 0; colorNdx < fbo->GetNumColorAttachments(); colorNdx++)
+ {
+ if (fbo->GetColorAttachment(colorNdx))
+ return colorNdx;
+ }
+
+ return -1;
+}
+
+static bool IsRenderSurfaceFormatCompatibleWithRGBA8Read (UInt32 format)
+{
+ switch (format)
+ {
+ case GL_RGBA8:
+ case GL_RGB8:
+ case GL_RG8:
+ case GL_R8:
+ case GL_RGB565:
+ case GL_RGB10_A2:
+ case GL_RGBA4:
+ case GL_RGB5_A1:
+ case GL_SRGB8_ALPHA8:
+ case GL_SRGB8:
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+static bool CanReadRGBA8FromFirstColorAttachment (const FramebufferObjectGLES30* fbo)
+{
+ int ndx = GetFirstValidColorAttachmentNdx(fbo);
+ if (ndx >= 0)
+ return IsRenderSurfaceFormatCompatibleWithRGBA8Read(fbo->GetColorAttachment(ndx)->GetFormat());
+ else
+ return false;
+}
+
+static void ReadPixelsFromDefaultFramebufferRGBA8 (int x, int y, int width, int height, UInt8* dstPtr)
+{
+ GLES_CHK(glReadPixels(x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, dstPtr));
+}
+
+static void ReadPixelsFromActiveFramebufferObjectRGBA8 (DeviceStateGLES30& state, int x, int y, int width, int height, UInt8* dstPtr)
+{
+ Assert(state.m_activeFbo != 0);
+ Assert(CanReadRGBA8FromFirstColorAttachment(state.m_activeFbo));
+
+ FramebufferObjectGLES30* activeFbo = state.m_activeFbo;
+ const int colorNdx = GetFirstValidColorAttachmentNdx(state.m_activeFbo);
+ const RenderSurfaceGLES30* colorSurface = activeFbo->GetColorAttachment(colorNdx);
+ const bool requiresResolve = colorSurface->GetNumSamples() > 1;
+
+ if (requiresResolve)
+ {
+ FramebufferObjectManagerGLES30& fboManager = GetFBOManager(state);
+ FramebufferObjectGLES30* resolveBuffer = GetResolveFramebufferObjectGLES30(&fboManager, colorSurface->GetFormat(), 0,
+ colorSurface->GetWidth(), colorSurface->GetHeight());
+
+ GLenum drawBuffer = GL_COLOR_ATTACHMENT0;
+ GLES_CHK(glDrawBuffers(1, &drawBuffer));
+ GLES_CHK(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resolveBuffer->GetFboID()));
+
+ // \note active FBO is already GL_READ_FRAMEBUFFER
+ GLES_CHK(glReadBuffer(GL_COLOR_ATTACHMENT0+colorNdx));
+
+ // Resolve blit.
+ GLES_CHK(glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST));
+
+ // Read from resolve buffer.
+ GLES_CHK(glReadBuffer(GL_COLOR_ATTACHMENT0));
+ GLES_CHK(glReadPixels(x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, dstPtr));
+
+ // Restore binding.
+ BindFramebufferObjectGLES30(activeFbo);
+ }
+ else
+ {
+ GLES_CHK(glReadBuffer(GL_COLOR_ATTACHMENT0+colorNdx));
+ GLES_CHK(glReadPixels(x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, dstPtr));
+ }
+}
+
+void GFX_GL_IMPL::ResolveDepthIntoTexture (RenderSurfaceHandle colorHandle, RenderSurfaceHandle depthHandle)
+{
+ FramebufferAttachmentsGLES30 att;
+ att.numColorAttachments = 1;
+ att.color[0] = static_cast<RenderSurfaceGLES30 *>(colorHandle.object);
+ att.depthStencil = static_cast<RenderSurfaceGLES30 *>(depthHandle.object);
+ att.cubemapFace = kCubeFaceUnknown;
+
+ FramebufferObjectGLES30 *helperFBO = GetFBOManager(STATE).GetFramebufferObject(att);
+ FramebufferObjectGLES30 *oldFBO = STATE.m_activeFbo;
+
+
+ // use the full size of the depth buffer, sub-rects are not needed and might not work on some hardware
+ GLint x = 0;
+ GLint y = 0;
+ GLint width = att.depthStencil->GetWidth();
+ GLint height = att.depthStencil->GetHeight();
+
+ // bind helper FBO
+ GLES_CHK(glBindFramebuffer(GL_FRAMEBUFFER, helperFBO->GetFboID()));
+
+ GLES_CHK(glReadBuffer (GL_NONE));
+
+ // blit
+ GLES_CHK(glBindFramebuffer (GL_READ_FRAMEBUFFER, oldFBO->GetFboID()));
+ GLES_CHK(glBindFramebuffer (GL_DRAW_FRAMEBUFFER, helperFBO->GetFboID()));
+ GLES_CHK(glBlitFramebuffer (x, y, x + width, y + height, x, y, x + width, y + height, GL_DEPTH_BUFFER_BIT, GL_NEAREST));
+
+ // restore the previously bound FBO
+ GLES_CHK(glBindFramebuffer (GL_FRAMEBUFFER, oldFBO->GetFboID()));
+
+
+}
+
+
+bool GFX_GL_IMPL::CaptureScreenshot (int left, int bottom, int width, int height, UInt8* rgba32)
+{
+ if (STATE.m_activeFbo)
+ {
+ if (CanReadRGBA8FromFirstColorAttachment(STATE.m_activeFbo))
+ {
+ ReadPixelsFromActiveFramebufferObjectRGBA8(STATE, left, bottom, width, height, rgba32);
+ return true;
+ }
+ else
+ {
+ ErrorString("Active FBO is not compatible with screenshots");
+ return false;
+ }
+ }
+ else
+ {
+ ReadPixelsFromDefaultFramebufferRGBA8(left, bottom, width, height, rgba32);
+ return true;
+ }
+}
+
+bool GFX_GL_IMPL::ReadbackImage (ImageReference& image, int left, int bottom, int width, int height, int destX, int destY)
+{
+ const bool coordsOk = destX == 0;
+ const bool formatOk = image.GetFormat() == kTexFormatRGBA32;
+ const bool strideOk = image.GetRowBytes() == 4*width;
+ const bool directRead = coordsOk && formatOk && strideOk;
+
+ if (directRead)
+ return CaptureScreenshot(left, bottom, width, height, image.GetRowPtr(destY));
+ else
+ {
+ std::vector<UInt8> tmpBuf(width*height*4);
+ if (!CaptureScreenshot(left, bottom, width, height, &tmpBuf[0]))
+ return false;
+
+ ImageReference blitSrc(width, height, 4*width, kTexFormatRGBA32, &tmpBuf[0]);
+ image.BlitImage(destX, destY, blitSrc);
+
+ return true;
+ }
+}
+
+void GFX_GL_IMPL::GrabIntoRenderTexture( RenderSurfaceHandle rs, RenderSurfaceHandle rd, int x, int y, int width, int height )
+{
+ if (!rs.IsValid())
+ return;
+
+ RenderSurfaceGLES30* dstColorSurface = static_cast<RenderSurfaceGLES30*>(rs.object);
+ RenderSurfaceGLES30* dstDepthSurface = static_cast<RenderSurfaceGLES30*>(rd.object);
+
+ // Grabbing to MSAA targets is not supported.
+ if (dstColorSurface->GetNumSamples() > 1 || (dstDepthSurface && dstDepthSurface->GetNumSamples() > 1))
+ {
+ ErrorString("GrabIntoRenderTexture(): Grabbing to MSAA RenderSurfaces is not supported");
+ return;
+ }
+
+ if (dstColorSurface->GetType() == RenderSurfaceGLES30::kTypeTexture2D && !dstDepthSurface && !STATE.m_activeFbo)
+ {
+ // Simple path: use glCopyTexSubImage()
+ RenderTexture2DGLES30* dstColorTex = static_cast<RenderTexture2DGLES30*>(dstColorSurface);
+
+ GetRealGfxDevice().SetTexture(kShaderFragment, 0, 0, dstColorTex->GetTextureID(), kTexDim2D, std::numeric_limits<float>::infinity());
+ GLES_CHK(glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, x, y, width, height));
+ }
+ else
+ {
+ // Temporary dst fbo.
+ FramebufferObjectManagerGLES30& fboManager = GetFBOManager(STATE);
+ FramebufferObjectGLES30* dstFbo = 0;
+ {
+ FramebufferAttachmentsGLES30 attachments;
+ attachments.numColorAttachments = 1;
+ attachments.color[0] = dstColorSurface;
+ attachments.depthStencil = dstDepthSurface;
+
+ dstFbo = fboManager.GetFramebufferObject(attachments);
+ }
+
+ bool copyColor = false;
+ bool copyDepth = false;
+ bool colorNeedsResolve = false;
+ bool depthNeedsResolve = false;
+ UInt32 srcColorFormat = 0;
+ UInt32 srcDepthFormat = 0;
+ GLenum srcColorAttachment = 0;
+
+ if (STATE.m_activeFbo)
+ {
+ const int srcColorNdx = GetFirstValidColorAttachmentNdx(STATE.m_activeFbo);
+ const RenderSurfaceGLES30* srcColorSurface = srcColorNdx >= 0 ? STATE.m_activeFbo->GetColorAttachment(srcColorNdx) : 0;
+ const RenderSurfaceGLES30* srcDepthSurface = STATE.m_activeFbo->GetDepthStencilAttachment() ? STATE.m_activeFbo->GetDepthStencilAttachment() : 0;
+
+ copyColor = srcColorSurface && dstColorSurface;
+ copyDepth = dstColorSurface && dstDepthSurface;
+ colorNeedsResolve = copyColor && srcColorSurface->GetNumSamples() > 1;
+ depthNeedsResolve = copyDepth && srcDepthSurface->GetNumSamples() > 1;
+ srcColorFormat = srcColorSurface ? srcColorSurface->GetFormat() : 0;
+ srcDepthFormat = srcDepthSurface ? srcDepthSurface->GetFormat() : 0;
+ srcColorAttachment = GL_COLOR_ATTACHMENT0 + srcColorNdx;
+ }
+ else
+ {
+ const bool isMSAA = queryInt(GL_SAMPLE_BUFFERS) > 0;
+
+ srcColorFormat = GetDefaultFramebufferColorFormatGLES30();
+ srcDepthFormat = GetDefaultFramebufferDepthFormatGLES30();
+ copyColor = srcColorFormat != 0 && dstColorSurface;
+ copyDepth = srcDepthFormat != 0 && dstDepthSurface;
+ colorNeedsResolve = isMSAA;
+ depthNeedsResolve = isMSAA;
+ srcColorAttachment = GL_BACK;
+ }
+
+ const bool colorFormatMatch = !copyColor || srcColorFormat == dstColorSurface->GetFormat();
+ const bool depthFormatMatch = !copyDepth || srcDepthFormat == dstDepthSurface->GetFormat();
+ const bool copyBoundsOk = x == 0 && y == 0;
+ const bool copyDirectly = (!colorNeedsResolve || (colorFormatMatch && copyBoundsOk)) && (!depthNeedsResolve || (depthFormatMatch && copyBoundsOk));
+ const UInt32 blitBuffers = (copyColor?GL_COLOR_BUFFER_BIT:0)|(copyDepth?GL_DEPTH_BUFFER_BIT:0);
+
+ // \note There are blits that are not supported altogether. For example blitting
+ // from unorm to integer format. Supporting them would require emulating the
+ // blit and even in such case the semantics are rather vague. So we just rely
+ // on GL giving an error if user attempts to do something strange.
+
+ if (copyDirectly)
+ {
+ GLenum drawBuffer = GL_COLOR_ATTACHMENT0;
+
+ GLES_CHK(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, dstFbo->GetFboID()));
+ GLES_CHK(glDrawBuffers(1, &drawBuffer));
+
+ // \note active FBO is already GL_READ_FRAMEBUFFER
+ GLES_CHK(glReadBuffer(srcColorAttachment));
+
+ GLES_CHK(glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, blitBuffers, GL_NEAREST));
+ }
+ else
+ {
+ Assert(colorNeedsResolve || depthNeedsResolve);
+
+ GLenum drawBuffer = GL_COLOR_ATTACHMENT0;
+ GLES_CHK(glDrawBuffers(1, &drawBuffer));
+
+ FramebufferObjectGLES30* resolveBuffer = GetResolveFramebufferObjectGLES30(&fboManager,
+ copyColor ? srcColorFormat : 0,
+ copyDepth ? srcDepthFormat : 0,
+ width, height);
+
+ GLES_CHK(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resolveBuffer->GetFboID()));
+
+ // \note active FBO is already GL_READ_FRAMEBUFFER
+ GLES_CHK(glReadBuffer(srcColorAttachment));
+
+ // Resolve blit.
+ GLES_CHK(glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, blitBuffers, GL_NEAREST));
+
+ // Blit from resolve buffer to destination.
+ GLES_CHK(glBindFramebuffer(GL_READ_FRAMEBUFFER, resolveBuffer->GetFboID()));
+ GLES_CHK(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, dstFbo->GetFboID()));
+ GLES_CHK(glReadBuffer(GL_COLOR_ATTACHMENT0));
+ GLES_CHK(glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, blitBuffers, GL_NEAREST));
+ }
+
+ // Restore readbuffer state.
+ GLES_CHK(glReadBuffer(GL_BACK));
+
+ // Restore binding.
+ if (!STATE.m_activeFbo)
+ BindDefaultFramebufferGLES30();
+ else
+ BindFramebufferObjectGLES30(STATE.m_activeFbo);
+ }
+}
+
+#if ENABLE_PROFILER
+
+void GFX_GL_IMPL::BeginProfileEvent(const char* name)
+{
+ if(gGraphicsCaps.gles30.hasDebugMarkers)
+ gGles3ExtFunc.glPushGroupMarkerEXT(0, name);
+}
+
+void GFX_GL_IMPL::EndProfileEvent()
+{
+ if(gGraphicsCaps.gles30.hasDebugMarkers)
+ gGles3ExtFunc.glPopGroupMarkerEXT();
+}
+
+GfxTimerQuery* GFX_GL_IMPL::CreateTimerQuery()
+{
+ if( gGraphicsCaps.hasTimerQuery )
+ return new TimerQueryGLES30;
+ return NULL;
+}
+
+void GFX_GL_IMPL::DeleteTimerQuery(GfxTimerQuery* query)
+{
+ delete query;
+}
+
+void GFX_GL_IMPL::BeginTimerQueries()
+{
+ if( !gGraphicsCaps.hasTimerQuery )
+ return;
+
+ g_TimerQueriesGLES30.BeginTimerQueries();
+}
+
+void GFX_GL_IMPL::EndTimerQueries()
+{
+ if( !gGraphicsCaps.hasTimerQuery )
+ return;
+
+ g_TimerQueriesGLES30.EndTimerQueries();
+}
+
+#endif // ENABLE_PROFILER
+
+typedef std::map<FixedFunctionStateGLES30, FixedFunctionProgramGLES30*, FullStateCompareGLES30> FFProgramCacheT;
+typedef std::map<FixedFunctionStateGLES30, GLShaderID, VertexStateCompareGLES30> FFVertexProgramCacheT;
+typedef std::map<FixedFunctionStateGLES30, GLShaderID, FragmentStateCompareGLES30> FFFragmentProgramCacheT;
+
+static FFProgramCacheT g_FixedFunctionProgramCache;
+static FFVertexProgramCacheT g_FFVertexProgramCache;
+static FFFragmentProgramCacheT g_FFFragmentProgramCache;
+
+static void ClearFixedFunctionPrograms()
+{
+ for (FFVertexProgramCacheT::iterator it = g_FFVertexProgramCache.begin(); it != g_FFVertexProgramCache.end(); ++it)
+ {
+ GLES_CHK(glDeleteShader(it->second));
+ }
+ g_FFVertexProgramCache.clear();
+ for (FFFragmentProgramCacheT::iterator it = g_FFFragmentProgramCache.begin(); it != g_FFFragmentProgramCache.end(); ++it)
+ {
+ GLES_CHK(glDeleteShader(it->second));
+ }
+ g_FFFragmentProgramCache.clear();
+
+ for(FFProgramCacheT::iterator i = g_FixedFunctionProgramCache.begin(); i != g_FixedFunctionProgramCache.end(); ++i)
+ {
+ delete i->second;
+ }
+ g_FixedFunctionProgramCache.clear();
+
+}
+
+static const FixedFunctionProgramGLES30* GetFixedFunctionProgram(const FixedFunctionStateGLES30& state)
+{
+ FFProgramCacheT::const_iterator cachedProgIt = g_FixedFunctionProgramCache.find(state);
+ if (cachedProgIt != g_FixedFunctionProgramCache.end())
+ return cachedProgIt->second;
+
+ // Cache miss, create fixed function program
+ // NOTE: don't worry too much about performance of vertex/fragment maps
+ // shader building/compilation is crazy expensive anyway
+ FFVertexProgramCacheT::const_iterator vertexProgIt = g_FFVertexProgramCache.find(state);
+ FFFragmentProgramCacheT::const_iterator fragmentProgIt = g_FFFragmentProgramCache.find(state);
+
+ GLShaderID vertexShader = (vertexProgIt != g_FFVertexProgramCache.end())? vertexProgIt->second: 0;
+ GLShaderID fragmentShader = (fragmentProgIt != g_FFFragmentProgramCache.end())? fragmentProgIt->second: 0;
+
+ if (vertexShader == 0)
+ {
+ vertexShader = glCreateShader(GL_VERTEX_SHADER);
+ std::string src = BuildVertexShaderSourceGLES30(state);
+ const char* cStr = src.c_str();
+
+ DBG_SHADER_VERBOSE_GLES30("Compiling generated vertex shader");
+ GlslGpuProgramGLES30::CompileGlslShader(vertexShader, cStr);
+ GLESAssert();
+
+ g_FFVertexProgramCache[state] = vertexShader;
+ }
+
+ if (fragmentShader == 0)
+ {
+ fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
+ std::string src = BuildFragmentShaderSourceGLES30(state);
+ const char* cStr = src.c_str();
+
+ DBG_SHADER_VERBOSE_GLES30("Compiling generated fragment shader");
+ GlslGpuProgramGLES30::CompileGlslShader(fragmentShader, cStr);
+ GLESAssert();
+
+ g_FFFragmentProgramCache[state] = fragmentShader;
+ }
+
+ DBG_SHADER_VERBOSE_GLES30("Creating and linking GLES program");
+ FixedFunctionProgramGLES30* ffProg = new FixedFunctionProgramGLES30(vertexShader, fragmentShader);
+ g_FixedFunctionProgramCache[state] = ffProg;
+
+ return ffProg;
+}
+
+static bool ComputeTextureTransformMatrix(TextureUnitStateGLES3 const& tex, Matrix4x4f const& worldViewMatrix, Matrix4x4f const& worldMatrix,
+ Matrix4x4f& outMatrix)
+{
+ switch (tex.texGen)
+ {
+ case kTexGenDisabled:
+ // NOTE: although tex-gen can be disabled
+ // textureMatrix can contain UV scale/offset
+ // so we will set it
+ case kTexGenObject:
+ if (tex.identityMatrix)
+ {
+ outMatrix.SetIdentity();
+ return false;
+ }
+ CopyMatrix(tex.textureMatrix.GetPtr(), outMatrix.GetPtr());
+ break;
+ case kTexGenSphereMap:
+ {
+ float invScale = 1.0f / Magnitude (worldViewMatrix.GetAxisX());
+
+ Matrix4x4f scaleOffsetMatrix;
+ scaleOffsetMatrix.SetScale(Vector3f(0.5*invScale, 0.5*invScale, 0.0));
+ scaleOffsetMatrix.SetPosition(Vector3f(0.5, 0.5, 0.0));
+
+ Matrix4x4f worldViewMatrixRotation = worldViewMatrix;
+ worldViewMatrixRotation.SetPosition(Vector3f::zero);
+ Matrix4x4f combo;
+ MultiplyMatrices4x4(&scaleOffsetMatrix, &worldViewMatrixRotation, &combo);
+ MultiplyMatrices4x4(&tex.textureMatrix, &combo, &outMatrix);
+ break;
+ }
+ case kTexGenEyeLinear:
+ MultiplyMatrices4x4(&tex.textureMatrix, &worldViewMatrix, &outMatrix);
+ break;
+ case kTexGenCubeNormal:
+ case kTexGenCubeReflect:
+ {
+ float invScale = 1.0f / Magnitude (worldMatrix.GetAxisX());
+ CopyMatrix(worldViewMatrix.GetPtr(), outMatrix.GetPtr());
+ outMatrix.Scale(Vector3f(invScale, invScale, invScale));
+ outMatrix.SetPosition(Vector3f::zero);
+ break;
+ }
+ default:
+ ErrorString( Format("Unknown TexGen mode %d", tex.texGen) );
+ }
+ return true;
+}
+
+void VBOContainsColorGLES30(bool flag)
+{
+ GFX_GL_IMPL& device = static_cast<GFX_GL_IMPL&>(GetRealGfxDevice());
+ GetGLES30DeviceState(device).vboContainsColor = flag;
+}
+
+void GLSLUseProgramGLES30(UInt32 programID)
+{
+ GFX_GL_IMPL& device = static_cast<GFX_GL_IMPL&>(GetRealGfxDevice());
+ if (GetGLES30DeviceState(device).activeProgramID == programID)
+ return;
+
+ GLES_CHK(glUseProgram (programID));
+ GetGLES30DeviceState(device).activeProgramID = programID;
+}
+
+static void UploadUniformMatrix4(BuiltinShaderParamIndices::MatrixParamData& matParam, const GpuProgramParameters::ConstantBufferList* constantBuffers, const float* dataPtr, ConstantBuffersGLES30& cbs)
+{
+ Assert(matParam.cols == 4 && matParam.rows == 4);
+ if (matParam.cbID == -1)
+ {
+ GLES_CHK(glUniformMatrix4fv (matParam.gpuIndex, 1, GL_FALSE, dataPtr));
+ }
+ else if (constantBuffers != NULL)
+ {
+ for (int i = 0; i < constantBuffers->size(); ++i)
+ {
+ if ((*constantBuffers)[i].m_Name.index == matParam.cbID)
+ {
+ const GpuProgramParameters::ConstantBuffer& cb = (*constantBuffers)[i];
+ int idx = cbs.FindAndBindCB(cb.m_Name.index, cb.m_BindIndex, cb.m_Size);
+ cbs.SetCBConstant(idx, matParam.gpuIndex, dataPtr, sizeof(Matrix4x4f));
+ break;
+ }
+ }
+ }
+}
+
+static void UploadUniformMatrix3(BuiltinShaderParamIndices::MatrixParamData& matParam, const GpuProgramParameters::ConstantBufferList* constantBuffers, const float* dataPtr, ConstantBuffersGLES30& cbs)
+{
+ Assert(matParam.cols == 3 && matParam.rows == 3);
+ if (matParam.cbID == -1)
+ {
+ GLES_CHK(glUniformMatrix3fv (matParam.gpuIndex, 1, GL_FALSE, dataPtr));
+ }
+ else if (constantBuffers != NULL)
+ {
+ for (int i = 0; i < constantBuffers->size(); ++i)
+ {
+ if ((*constantBuffers)[i].m_Name.index == matParam.cbID)
+ {
+ const GpuProgramParameters::ConstantBuffer& cb = (*constantBuffers)[i];
+ int idx = cbs.FindAndBindCB(cb.m_Name.index, cb.m_BindIndex, cb.m_Size);
+ cbs.SetCBConstant(idx, matParam.gpuIndex, dataPtr, sizeof(Matrix3x3f));
+ break;
+ }
+ }
+ }
+}
+
+void GFX_GL_IMPL::BeforeDrawCall(bool immediateMode)
+{
+ DBG_LOG_GLES30("BeforeDrawCall(%s)", GetBoolString(immediateMode));
+
+ ShaderLab::PropertySheet *props = ShaderLab::g_GlobalProperties;
+ Assert(props);
+
+ // WorldView Matrix
+ STATE.transformState.UpdateWorldViewMatrix (m_BuiltinParamValues);
+
+ // Materials
+ if (STATE.lighting)
+ {
+ m_BuiltinParamValues.SetVectorParam(kShaderVecFFMatEmission, Vector4f(STATE.matEmissive.GetPtr()));
+ m_BuiltinParamValues.SetVectorParam(kShaderVecFFMatAmbient, Vector4f(STATE.matAmbient.GetPtr()));
+ m_BuiltinParamValues.SetVectorParam(kShaderVecFFMatDiffuse, Vector4f(STATE.matDiffuse.GetPtr()));
+ m_BuiltinParamValues.SetVectorParam(kShaderVecFFMatSpecular, Vector4f(STATE.matSpecular.GetPtr()));
+ m_BuiltinParamValues.SetVectorParam(kShaderVecFFMatShininess, Vector4f(STATE.matShininess, STATE.matShininess, STATE.matShininess, STATE.matShininess));
+ }
+
+ // Fog
+ if (m_FogParams.mode > kFogDisabled)
+ {
+ float diff = m_FogParams.mode == kFogLinear ? m_FogParams.end - m_FogParams.start : 0.0f;
+ float invDiff = Abs(diff) > 0.0001f ? 1.0f/diff : 0.0f;
+
+ m_BuiltinParamValues.SetVectorParam(kShaderVecFFFogColor, m_FogParams.color);
+ m_BuiltinParamValues.SetVectorParam(kShaderVecFFFogParams, Vector4f(m_FogParams.density * 1.2011224087f,
+ m_FogParams.density * 1.4426950408f,
+ m_FogParams.mode == kFogLinear ? -invDiff : 0.0f,
+ m_FogParams.mode == kFogLinear ? m_FogParams.end * invDiff : 0.0f
+ ));
+ }
+
+ // Alpha-test
+ m_BuiltinParamValues.SetVectorParam(kShaderVecFFAlphaTestRef, Vector4f(STATE.alphaValue, STATE.alphaValue, STATE.alphaValue, STATE.alphaValue));
+
+ UniformCacheGLES30* targetCache = 0;
+ const GpuProgramParameters::ConstantBufferList* constantBuffers = NULL;
+ if (STATE.activeProgram)
+ {
+ // Apply GPU program
+ GlslGpuProgramGLES30& prog = static_cast<GlslGpuProgramGLES30&>(*STATE.activeProgram);
+ int fogIndex = prog.ApplyGpuProgramES30 (*STATE.activeProgramParams, STATE.activeProgramParamsBuffer.data());
+ constantBuffers = &STATE.activeProgramParams->GetConstantBuffers();
+ m_BuiltinParamIndices[kShaderVertex] = &STATE.activeProgramParams->GetBuiltinParams();
+
+ targetCache = &prog.m_UniformCache[fogIndex];
+ }
+ else
+ {
+ // Emulate Fixed Function pipe
+ m_BuiltinParamValues.SetVectorParam(kShaderVecFFColor, Vector4f(STATE.color.GetPtr()));
+ for (int i = 0; i < STATE.textureCount; ++i)
+ {
+ m_BuiltinParamValues.SetVectorParam(BuiltinShaderVectorParam(kShaderVecFFTextureEnvColor0+i), STATE.textures[i].color);
+ }
+
+ // generate program from fixed function state
+ DBG_LOG_GLES30(" using fixed-function");
+ FixedFunctionStateGLES30 ffstate;
+ STATE.ComputeFixedFunctionState(ffstate, m_FogParams);
+ const FixedFunctionProgramGLES30* program = GetFixedFunctionProgram(ffstate);
+ program->ApplyFFGpuProgram(m_BuiltinParamValues, STATE.m_CBs);
+ m_BuiltinParamIndices[kShaderVertex] = &program->GetBuiltinParams();
+ constantBuffers = &program->GetConstantBuffers();
+
+ targetCache = &program->m_UniformCache;
+ }
+
+ // Set Unity built-in parameters
+ {
+ Assert(m_BuiltinParamIndices[kShaderVertex]);
+ const BuiltinShaderParamIndices& params = *m_BuiltinParamIndices[kShaderVertex];
+
+ // MVP matrix
+ if (params.mat[kShaderInstanceMatMVP].gpuIndex >= 0)
+ {
+ Matrix4x4f wvp;
+ MultiplyMatrices4x4(&m_BuiltinParamValues.GetMatrixParam(kShaderMatProj), &STATE.transformState.worldViewMatrix, &wvp);
+
+ BuiltinShaderParamIndices::MatrixParamData matParam = params.mat[kShaderInstanceMatMVP];
+ Assert(matParam.rows == 4 && matParam.cols == 4);
+ UploadUniformMatrix4(matParam, constantBuffers, wvp.GetPtr(), STATE.m_CBs);
+ }
+ // MV matrix
+ if (params.mat[kShaderInstanceMatMV].gpuIndex >= 0)
+ {
+ BuiltinShaderParamIndices::MatrixParamData matParam = params.mat[kShaderInstanceMatMV];
+ Assert(matParam.rows == 4 && matParam.cols == 4);
+ UploadUniformMatrix4(matParam, constantBuffers, STATE.transformState.worldViewMatrix.GetPtr(), STATE.m_CBs);
+ }
+ // Transpose of MV matrix
+ if (params.mat[kShaderInstanceMatTransMV].gpuIndex >= 0)
+ {
+ Matrix4x4f tWV;
+ TransposeMatrix4x4(&STATE.transformState.worldViewMatrix, &tWV);
+
+ BuiltinShaderParamIndices::MatrixParamData matParam = params.mat[kShaderInstanceMatTransMV];
+ Assert(matParam.rows == 4 && matParam.cols == 4);
+ UploadUniformMatrix4(matParam, constantBuffers, tWV.GetPtr(), STATE.m_CBs);
+ }
+ // Inverse transpose of MV matrix
+ if (params.mat[kShaderInstanceMatInvTransMV].gpuIndex >= 0)
+ {
+ // Inverse transpose of modelview should be scaled by uniform
+ // normal scale (this will match state.matrix.invtrans.modelview
+ // and gl_NormalMatrix in OpenGL)
+ Matrix4x4f mat = STATE.transformState.worldViewMatrix;
+ if (STATE.normalization == kNormalizationScale)
+ {
+ float invScale = m_BuiltinParamValues.GetInstanceVectorParam(kShaderInstanceVecScale).w;
+ mat.Get (0, 0) *= invScale;
+ mat.Get (1, 0) *= invScale;
+ mat.Get (2, 0) *= invScale;
+ mat.Get (0, 1) *= invScale;
+ mat.Get (1, 1) *= invScale;
+ mat.Get (2, 1) *= invScale;
+ mat.Get (0, 2) *= invScale;
+ mat.Get (1, 2) *= invScale;
+ mat.Get (2, 2) *= invScale;
+ }
+ Matrix4x4f invWV, tInvWV;
+ Matrix4x4f::Invert_General3D (mat, invWV);
+ TransposeMatrix4x4(&invWV, &tInvWV);
+
+ BuiltinShaderParamIndices::MatrixParamData matParam = params.mat[kShaderInstanceMatInvTransMV];
+ Assert(matParam.rows == 4 && matParam.cols == 4);
+ UploadUniformMatrix4(matParam, constantBuffers, tInvWV.GetPtr(), STATE.m_CBs);
+ }
+ // M matrix
+ if (params.mat[kShaderInstanceMatM].gpuIndex >= 0)
+ {
+ BuiltinShaderParamIndices::MatrixParamData matParam = params.mat[kShaderInstanceMatM];
+ const Matrix4x4f& mat = STATE.transformState.worldMatrix;
+ Assert(matParam.rows == 4 && matParam.cols == 4);
+ UploadUniformMatrix4(matParam, constantBuffers, mat.GetPtr(), STATE.m_CBs);
+ }
+ // Inverse M matrix
+ if (params.mat[kShaderInstanceMatInvM].gpuIndex >= 0)
+ {
+ BuiltinShaderParamIndices::MatrixParamData matParam = params.mat[kShaderInstanceMatInvM];
+ Matrix4x4f mat = STATE.transformState.worldMatrix;
+ if (STATE.normalization == kNormalizationScale)
+ {
+ // Kill scale in the world matrix before inverse
+ float invScale = m_BuiltinParamValues.GetInstanceVectorParam(kShaderInstanceVecScale).w;
+ mat.Get (0, 0) *= invScale;
+ mat.Get (1, 0) *= invScale;
+ mat.Get (2, 0) *= invScale;
+ mat.Get (0, 1) *= invScale;
+ mat.Get (1, 1) *= invScale;
+ mat.Get (2, 1) *= invScale;
+ mat.Get (0, 2) *= invScale;
+ mat.Get (1, 2) *= invScale;
+ mat.Get (2, 2) *= invScale;
+ }
+ Matrix4x4f inverseMat;
+ Matrix4x4f::Invert_General3D (mat, inverseMat);
+ Assert(matParam.rows == 4 && matParam.cols == 4);
+ UploadUniformMatrix4(matParam, constantBuffers, inverseMat.GetPtr(), STATE.m_CBs);
+ }
+
+ // Normal matrix
+ if (params.mat[kShaderInstanceMatNormalMatrix].gpuIndex >= 0)
+ {
+ BuiltinShaderParamIndices::MatrixParamData matParam = params.mat[kShaderInstanceMatNormalMatrix];
+
+ // @TBD: remove normalization in fixed function emulation after Normal matrix multiply.
+ Matrix4x4f rotWV;
+ rotWV = STATE.transformState.worldViewMatrix;
+ rotWV.SetPosition(Vector3f::zero); // reset translation
+
+ if (STATE.normalization == kNormalizationScale) // reset scale
+ {
+ float invScale = m_BuiltinParamValues.GetInstanceVectorParam(kShaderInstanceVecScale).w;
+ rotWV.Get (0, 0) *= invScale;
+ rotWV.Get (1, 0) *= invScale;
+ rotWV.Get (2, 0) *= invScale;
+ rotWV.Get (0, 1) *= invScale;
+ rotWV.Get (1, 1) *= invScale;
+ rotWV.Get (2, 1) *= invScale;
+ rotWV.Get (0, 2) *= invScale;
+ rotWV.Get (1, 2) *= invScale;
+ rotWV.Get (2, 2) *= invScale;
+ }
+ Matrix3x3f rotWV33 = Matrix3x3f(rotWV);
+ UploadUniformMatrix3(matParam, constantBuffers, rotWV33.GetPtr(), STATE.m_CBs);
+ }
+
+ // Set instance vector parameters
+ for (int i = 0; i < kShaderInstanceVecCount; ++i)
+ {
+ int gpuIndexVS = params.vec[i].gpuIndex;
+ if (gpuIndexVS >= 0)
+ {
+ const float* val = m_BuiltinParamValues.GetInstanceVectorParam((ShaderBuiltinInstanceVectorParam)i).GetPtr();
+ switch (params.vec[i].dim) {
+ case 1: CachedUniform1(targetCache, gpuIndexVS, val); break;
+ case 2: CachedUniform2(targetCache, gpuIndexVS, val); break;
+ case 3: CachedUniform3(targetCache, gpuIndexVS, val); break;
+ case 4: CachedUniform4(targetCache, gpuIndexVS, val); break;
+ }
+ GLESAssert();
+ }
+ }
+
+ // Texture Matrices
+ Matrix4x4f texM;
+ for (int i = 0; i < kMaxSupportedTextureUnitsGLES; ++i)
+ {
+ BuiltinShaderParamIndices::MatrixParamData matParam = params.mat[kShaderInstanceMatTexture0 + i];
+ if (matParam.gpuIndex >= 0)
+ {
+ if (i < STATE.textureCount)
+ ComputeTextureTransformMatrix(STATE.textures[i], STATE.transformState.worldViewMatrix, STATE.transformState.worldMatrix, texM);
+ else
+ texM.SetIdentity();
+
+ Assert(matParam.rows == 4 && matParam.cols == 4);
+ UploadUniformMatrix4(matParam, constantBuffers, texM.GetPtr(), STATE.m_CBs);
+ }
+ }
+ }
+
+ // Set per-drawcall properties
+ GpuProgram* subprogram = STATE.activeProgram;
+ if (subprogram)
+ {
+ const MaterialPropertyBlock::Property* curProp = STATE.m_MaterialProperties.GetPropertiesBegin();
+ const MaterialPropertyBlock::Property* propEnd = STATE.m_MaterialProperties.GetPropertiesEnd();
+ const float* propBuffer = STATE.m_MaterialProperties.GetBufferBegin();
+ while (curProp != propEnd)
+ {
+ FastPropertyName name;
+ name.index = curProp->nameIndex;
+ const GpuProgramParameters::ValueParameter* param = STATE.activeProgramParams->FindParam(name);
+ if (param && curProp->rows == param->m_RowCount)
+ {
+ if (curProp->rows == 1)
+ {
+ const float* src = &propBuffer[curProp->offset];
+ switch (param->m_ColCount) {
+ case 1: CachedUniform1(targetCache, param->m_Index, src); break;
+ case 2: CachedUniform2(targetCache, param->m_Index, src); break;
+ case 3: CachedUniform3(targetCache, param->m_Index, src); break;
+ case 4: CachedUniform4(targetCache, param->m_Index, src); break;
+ }
+ GLESAssert();
+ }
+ else if (curProp->rows == 4)
+ {
+ DebugAssert(curProp->cols == 4);
+ const Matrix4x4f* mat = (const Matrix4x4f*)&propBuffer[curProp->offset];
+ GLES_CHK(glUniformMatrix4fv (param->m_Index, 1, GL_FALSE, mat->GetPtr()));
+ }
+ else
+ {
+ AssertString("Unknown property dimensions");
+ }
+ }
+ ++curProp;
+ }
+ }
+ STATE.m_MaterialProperties.Clear();
+
+ STATE.m_CBs.UpdateBuffers ();
+}
+
+bool GFX_GL_IMPL::IsPositionRequiredForTexGen(int unit) const
+{
+ if (unit >= STATE.textureCount)
+ return false;
+ if (STATE.activeProgram)
+ return false;
+
+ //DebugAssertIf( unit < 0 || unit >= gGraphicsCaps.maxTexUnits);
+ const TextureUnitStateGLES3& unitState = STATE.textures[unit];
+ return TextureUnitStateGLES3::PositionRequiredForTexGen(unitState.texGen);
+}
+
+bool GFX_GL_IMPL::IsNormalRequiredForTexGen(int unit) const
+{
+ if (unit >= STATE.textureCount)
+ return false;
+ if (STATE.activeProgram)
+ return false;
+
+ //DebugAssertIf( unit < 0 || unit >= gGraphicsCaps.maxTexUnits );
+ const TextureUnitStateGLES3& unitState = STATE.textures[unit];
+ return TextureUnitStateGLES3::NormalRequiredForTexGen(unitState.texGen);
+}
+
+bool GFX_GL_IMPL::IsPositionRequiredForTexGen() const
+{
+ return ( STATE.positionTexGen != 0 && !STATE.activeProgram );
+}
+
+bool GFX_GL_IMPL::IsNormalRequiredForTexGen() const
+{
+ return ( STATE.normalTexGen != 0 && !STATE.activeProgram );
+}
+
+void* GFX_GL_IMPL::GetNativeTexturePointer(TextureID id)
+{
+ return (void*)TextureIdMap::QueryNativeTexture(id);
+}
+
+void GFX_GL_IMPL::ReloadResources()
+{
+ // Buffers in BufferManager must be cleared before recreating VBOs.
+ GetBufferManagerGLES30()->InvalidateAll();
+
+ RecreateAllVBOs();
+ GfxDevice::CommonReloadResources(kReleaseRenderTextures | kReloadShaders | kReloadTextures);
+ ClearFixedFunctionPrograms();
+
+ if (STATE.m_fboManager)
+ STATE.m_fboManager->InvalidateObjects();
+
+ InvalidateState();
+}
+
+// GPU skinning functionality
+GPUSkinningInfo * GFX_GL_IMPL::CreateGPUSkinningInfo()
+{
+ if (gGraphicsCaps.gles30.useTFSkinning)
+ return new TransformFeedbackSkinningInfo();
+ else
+ return 0;
+}
+
+void GFX_GL_IMPL::DeleteGPUSkinningInfo(GPUSkinningInfo *info)
+{
+ delete reinterpret_cast<TransformFeedbackSkinningInfo *>(info);
+}
+
+// All actual functionality is performed in TransformFeedbackSkinningInfo, just forward the calls
+void GFX_GL_IMPL::SkinOnGPU( GPUSkinningInfo * info, bool lastThisFrame )
+{
+ reinterpret_cast<TransformFeedbackSkinningInfo *>(info)->SkinMesh(lastThisFrame);
+}
+
+void GFX_GL_IMPL::UpdateSkinSourceData(GPUSkinningInfo *info, const void *vertData, const BoneInfluence *skinData, bool dirty)
+{
+ reinterpret_cast<TransformFeedbackSkinningInfo *>(info)->UpdateSourceData(vertData, skinData, dirty);
+}
+
+void GFX_GL_IMPL::UpdateSkinBonePoses(GPUSkinningInfo *info, const int boneCount, const Matrix4x4f* poses)
+{
+ reinterpret_cast<TransformFeedbackSkinningInfo *>(info)->UpdateSourceBones(boneCount, poses);
+}
+
+// Acquire thread ownership on the calling thread. Worker releases ownership.
+void GFX_GL_IMPL::AcquireThreadOwnership()
+{
+ AcquireGLES30Context();
+}
+
+// Release thread ownership on the calling thread. Worker acquires ownership.
+void GFX_GL_IMPL::ReleaseThreadOwnership()
+{
+ ReleaseGLES30Context();
+}
+
+
+
+// ---------- verify state
+#if GFX_DEVICE_VERIFY_ENABLE
+void GFX_GL_IMPL::VerifyState()
+{
+}
+
+#endif // GFX_DEVICE_VERIFY_ENABLE
+
+#endif // GFX_SUPPORTS_OPENGLES30
diff --git a/Runtime/GfxDevice/opengles30/GfxDeviceGLES30.h b/Runtime/GfxDevice/opengles30/GfxDeviceGLES30.h
new file mode 100644
index 0000000..dfc716b
--- /dev/null
+++ b/Runtime/GfxDevice/opengles30/GfxDeviceGLES30.h
@@ -0,0 +1,191 @@
+#pragma once
+
+#if GFX_DEVICE_VIRTUAL
+
+#ifdef GFX_GL_IMPL
+#undef GFX_GL_IMPL
+#endif
+#define GFX_GL_IMPL GfxDeviceGLES30
+
+class GFX_GL_IMPL : public GfxThreadableDevice {
+public:
+ GFX_GL_IMPL();
+ GFX_API ~GFX_GL_IMPL();
+
+ GFX_API void InvalidateState();
+ #if GFX_DEVICE_VERIFY_ENABLE
+ GFX_API void VerifyState();
+ #endif
+
+ GFX_API void Clear (UInt32 clearFlags, const float color[4], float depth, int stencil);
+ GFX_API void SetUserBackfaceMode( bool enable );
+ GFX_API void SetWireframe(bool wire) { } // not possible in GLES
+ GFX_API bool GetWireframe() const { return false; } // not possible in GLES
+ GFX_API void SetInvertProjectionMatrix( bool enable );
+ GFX_API bool GetInvertProjectionMatrix() const;
+
+ GFX_API void SetWorldMatrix( const float matrix[16] );
+ GFX_API void SetViewMatrix( const float matrix[16] );
+
+ GFX_API void SetProjectionMatrix (const Matrix4x4f& matrix);
+ GFX_API void GetMatrix( float outMatrix[16] ) const;
+
+ GFX_API const float* GetWorldMatrix() const ;
+ GFX_API const float* GetViewMatrix() const;
+ GFX_API const float* GetProjectionMatrix() const;
+ GFX_API const float* GetDeviceProjectionMatrix() const;
+
+ GFX_API void SetNormalizationBackface( NormalizationMode mode, bool backface );
+ GFX_API void SetFFLighting( bool on, bool separateSpecular, ColorMaterialMode colorMaterial );
+ GFX_API void SetMaterial( const float ambient[4], const float diffuse[4], const float specular[4], const float emissive[4], const float shininess );
+ GFX_API void SetColor( const float color[4] );
+ GFX_API void SetViewport( int x, int y, int width, int height );
+ GFX_API void GetViewport( int* values ) const;
+
+ GFX_API void SetScissorRect( int x, int y, int width, int height );
+ GFX_API void DisableScissor();
+ GFX_API bool IsScissorEnabled() const;
+ GFX_API void GetScissorRect( int values[4] ) const;
+ GFX_API void SetSRGBWrite (const bool);
+ GFX_API bool GetSRGBWrite ();
+
+ GFX_API void ResolveDepthIntoTexture (RenderSurfaceHandle colorHandle, RenderSurfaceHandle depthHandle);
+
+ GFX_API TextureCombinersHandle CreateTextureCombiners( int count, const ShaderLab::TextureBinding* texEnvs, const ShaderLab::PropertySheet* props, bool hasVertexColorOrLighting, bool usesAddSpecular );
+ GFX_API void DeleteTextureCombiners( TextureCombinersHandle& textureCombiners );
+ GFX_API void SetTextureCombiners( TextureCombinersHandle textureCombiners, const ShaderLab::PropertySheet* props );
+
+ GFX_API void SetTexture (ShaderType shaderType, int unit, int samplerUnit, TextureID texture, TextureDimension dim, float bias);
+ GFX_API void SetTextureParams( TextureID texture, TextureDimension texDim, TextureFilterMode filter, TextureWrapMode wrap, int anisoLevel, bool hasMipMap, TextureColorSpace colorSpace );
+ GFX_API void SetTextureTransform( int unit, TextureDimension dim, TexGenMode texGen, bool identity, const float matrix[16]);
+ GFX_API void SetTextureName ( TextureID texture, const char* name ) { }
+
+ GFX_API void SetMaterialProperties( const MaterialPropertyBlock& block );
+
+ GFX_API void SetShadersThreadable (GpuProgram* programs[kShaderTypeCount], const GpuProgramParameters* params[kShaderTypeCount], UInt8 const * const paramsBuffer[kShaderTypeCount]);
+ GFX_API void CreateShaderParameters( ShaderLab::SubProgram* program, FogMode fogMode );
+
+ GFX_API bool IsCombineModeSupported( unsigned int combiner );
+ GFX_API void SetTextureCombinersThreadable( TextureCombinersHandle textureCombiners, const TexEnvData* texEnvData, const Vector4f* texColors );
+
+ GFX_API bool IsShaderActive( ShaderType type ) const;
+ GFX_API void DestroySubProgram( ShaderLab::SubProgram* subprogram );
+ GFX_API void SetConstantBufferInfo( int id, int size );
+
+ GFX_API void DisableLights( int startLight );
+ GFX_API void SetLight( int light, const GfxVertexLight& data);
+ GFX_API void SetAmbient( const float ambient[4] );
+
+ GFX_API void EnableFog (const GfxFogParams& fog);
+ GFX_API void DisableFog();
+
+ GFX_API VBO* CreateVBO();
+ GFX_API void DeleteVBO( VBO* vbo );
+ GFX_API DynamicVBO& GetDynamicVBO();
+
+ GFX_API RenderSurfaceHandle CreateRenderColorSurface (TextureID textureID, int width, int height, int samples, int depth, TextureDimension dim, RenderTextureFormat format, UInt32 createFlags);
+ GFX_API RenderSurfaceHandle CreateRenderDepthSurface (TextureID textureID, int width, int height, int samples, TextureDimension dim, DepthBufferFormat depthFormat, UInt32 createFlags);
+ GFX_API void SetRenderTargets (int count, RenderSurfaceHandle* colorHandles, RenderSurfaceHandle depthHandle, int mipLevel, CubemapFace face = kCubeFaceUnknown);
+ GFX_API void DestroyRenderSurface (RenderSurfaceHandle& rs);
+ GFX_API void ResolveColorSurface (RenderSurfaceHandle srcHandle, RenderSurfaceHandle dstHandle);
+ GFX_API RenderSurfaceHandle GetActiveRenderColorSurface (int index);
+ GFX_API RenderSurfaceHandle GetActiveRenderDepthSurface ();
+ GFX_API void SetSurfaceFlags(RenderSurfaceHandle surf, UInt32 flags, UInt32 keepFlags);
+ GFX_API void DiscardContents (RenderSurfaceHandle& rs);
+
+ GFX_API void UploadTexture2D( TextureID texture, TextureDimension dimension, UInt8* srcData, int srcSize, int width, int height, TextureFormat format, int mipCount, UInt32 uploadFlags, int skipMipLevels, TextureUsageMode usageMode, TextureColorSpace colorSpace );
+ GFX_API void UploadTextureSubData2D( TextureID texture, UInt8* srcData, int srcSize, int mipLevel, int x, int y, int width, int height, TextureFormat format, TextureColorSpace colorSpace );
+ GFX_API void UploadTextureCube( TextureID texture, UInt8* srcData, int srcSize, int faceDataSize, int size, TextureFormat format, int mipCount, UInt32 uploadFlags, TextureColorSpace colorSpace );
+ GFX_API void UploadTexture3D( TextureID texture, UInt8* srcData, int srcSize, int width, int height, int depth, TextureFormat format, int mipCount, UInt32 uploadFlags );
+ GFX_API void DeleteTexture( TextureID texture );
+
+ GFX_API PresentMode GetPresentMode();
+
+ GFX_API void BeginFrame();
+ GFX_API void EndFrame();
+ GFX_API void PresentFrame();
+
+ GFX_API bool IsValidState();
+ GFX_API bool HandleInvalidState();
+
+ GFX_API void FinishRendering();
+
+ // Immediate mode rendering
+ GFX_API void ImmediateVertex( float x, float y, float z );
+ GFX_API void ImmediateNormal( float x, float y, float z );
+ GFX_API void ImmediateColor( float r, float g, float b, float a );
+ GFX_API void ImmediateTexCoordAll( float x, float y, float z );
+ GFX_API void ImmediateTexCoord( int unit, float x, float y, float z );
+ GFX_API void ImmediateBegin( GfxPrimitiveType type );
+ GFX_API void ImmediateEnd();
+
+ // Acquire thread ownership on the calling thread. Worker releases ownership.
+ GFX_API void AcquireThreadOwnership();
+ // Release thread ownership on the calling thread. Worker acquires ownership.
+ GFX_API void ReleaseThreadOwnership();
+
+ protected:
+
+ GFX_API bool CaptureScreenshot( int left, int bottom, int width, int height, UInt8* rgba32 );
+ GFX_API bool ReadbackImage( ImageReference& image, int left, int bottom, int width, int height, int destX, int destY );
+ GFX_API void GrabIntoRenderTexture( RenderSurfaceHandle rs, RenderSurfaceHandle rd, int x, int y, int width, int height );
+
+ GFX_API void BeforeDrawCall( bool immediateMode);
+
+ GFX_API void SetBlendState(const DeviceBlendState* state, float alphaRef);
+ GFX_API void SetRasterState(const DeviceRasterState* state);
+ GFX_API void SetDepthState(const DeviceDepthState* state);
+ GFX_API void SetStencilState(const DeviceStencilState* state, int stencilRef);
+
+ GFX_API DeviceBlendState* CreateBlendState(const GfxBlendState& state);
+ GFX_API DeviceDepthState* CreateDepthState(const GfxDepthState& state);
+ GFX_API DeviceStencilState* CreateStencilState(const GfxStencilState& state);
+ GFX_API DeviceRasterState* CreateRasterState(const GfxRasterState& state);
+
+ GFX_API GPUSkinningInfo *CreateGPUSkinningInfo();
+ GFX_API void DeleteGPUSkinningInfo(GPUSkinningInfo *info);
+ GFX_API void SkinOnGPU( GPUSkinningInfo * info, bool lastThisFrame );
+ GFX_API void UpdateSkinSourceData(GPUSkinningInfo *info, const void *vertData, const BoneInfluence *skinData, bool dirty);
+ GFX_API void UpdateSkinBonePoses(GPUSkinningInfo *info, const int boneCount, const Matrix4x4f* poses);
+
+#if ENABLE_PROFILER
+ GFX_API void BeginProfileEvent (const char* name);
+ GFX_API void EndProfileEvent ();
+
+ GFX_API GfxTimerQuery* CreateTimerQuery();
+ GFX_API void DeleteTimerQuery(GfxTimerQuery* query);
+ GFX_API void BeginTimerQueries();
+ GFX_API void EndTimerQueries();
+#endif
+
+public:
+ // OpenGLES specific
+ GFX_API void ReloadResources();
+ GFX_API bool IsPositionRequiredForTexGen(int texStageIndex) const;
+ GFX_API bool IsNormalRequiredForTexGen(int texStageIndex) const;
+
+ GFX_API bool IsPositionRequiredForTexGen() const;
+ GFX_API bool IsNormalRequiredForTexGen() const;
+
+ GFX_API void* GetNativeTexturePointer(TextureID id);
+
+ DeviceStateGLES30& GetState() { return state; }
+
+private:
+ DeviceStateGLES30 state;
+};
+
+#define STATE this->state
+#define GetGLES30DeviceState(device) device.GetState()
+
+#else // GFX_DEVICE_VIRTUAL
+
+
+struct GfxDeviceImpl {
+ DeviceStateGLES30 state;
+};
+
+#define STATE impl->state
+#define GetGLES30DeviceState(device) device.GetImpl()->state
+
+#endif // GFX_DEVICE_VIRTUAL
diff --git a/Runtime/GfxDevice/opengles30/GpuProgramsGLES30.cpp b/Runtime/GfxDevice/opengles30/GpuProgramsGLES30.cpp
new file mode 100644
index 0000000..42d468f
--- /dev/null
+++ b/Runtime/GfxDevice/opengles30/GpuProgramsGLES30.cpp
@@ -0,0 +1,1176 @@
+#include "UnityPrefix.h"
+#if GFX_SUPPORTS_OPENGLES30
+#include "GpuProgramsGLES30.h"
+#include "Runtime/Utilities/LogAssert.h"
+#include "Runtime/Utilities/ArrayUtility.h"
+#include "Runtime/Utilities/File.h"
+#include "Runtime/File/ApplicationSpecificPersistentDataPath.h"
+#include "Runtime/Shaders/GraphicsCaps.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+#include "External/shaderlab/Library/ShaderLabErrors.h"
+#include "Runtime/GfxDevice/ChannelAssigns.h"
+#include "External/shaderlab/Library/shaderlab.h"
+#include "External/shaderlab/Library/texenv.h"
+#include "External/shaderlab/Library/program.h"
+#include "Runtime/Math/Matrix4x4.h"
+#include "Runtime/Math/Matrix3x3.h"
+#include "Runtime/Math/Vector4.h"
+
+
+#include "IncludesGLES30.h"
+#include "AssertGLES30.h"
+#include "GpuPropertiesGLES30.h"
+#include "Runtime/Utilities/GLSLUtilities.h"
+#include "VBOGLES30.h"
+#include "ConstantBuffersGLES30.h"
+#include "DebugGLES30.h"
+
+#if UNITY_ANDROID
+ #include "PlatformDependent/AndroidPlayer/EntryPoint.h"
+ #include "PlatformDependent/AndroidPlayer/AndroidSystemInfo.h"
+#endif
+
+#if UNITY_BLACKBERRY
+ #include "ctype.h"
+#endif
+
+
+#include <stdio.h>
+
+#define DEBUG_GLSL_BINDINGS 0
+#define DEBUG_SHADER_CACHE 0
+
+
+// from shader_yacc.hpp
+extern std::string g_LastParsedShaderName;
+
+void GLSLUseProgramGLES30 (UInt32 programID); // defined in GfxDeviceGLES30.cpp
+
+static bool CompileGLSLVertexShader (const std::string& source, ChannelAssigns& channels, GLShaderID programID, GLShaderID parentProgramID, GLShaderID* outShaderID);
+static bool CompileGLSLFragmentShader (const std::string& source, GLShaderID* outShaderID);
+static bool BindVProgAttrbutes(const std::string& source, ChannelAssigns& channels, GLShaderID programID);
+static bool RebindVProgAttrbutes(GLShaderID programID, GLShaderID parentProgramID);
+
+
+static void GetCachedBinaryName(const std::string& vprog, const std::string& fshader, char filename[33]);
+
+
+// --------------------------------------------------------------------------
+// GLSL
+
+// AttributeConversionTable
+const static UInt32 kAttribLookupTableSize = 12;
+
+const static char* s_GLSLESAttributes[kAttribLookupTableSize] = {
+ "_glesVertex", "_glesColor", "_glesNormal",
+ "_gles_unused__", // unused
+ "_glesMultiTexCoord0", "_glesMultiTexCoord1", "_glesMultiTexCoord2", "_glesMultiTexCoord3",
+ "_glesMultiTexCoord4", "_glesMultiTexCoord5", "_glesMultiTexCoord6", "_glesMultiTexCoord7"
+};
+const static char* s_UnityAttributes[kAttribLookupTableSize] = {
+ "Vertex", "Color", "Normal",
+ "", // unused
+ "TexCoord", "TexCoord1", "TexCoord2", "TexCoord3",
+ "TexCoord4", "TexCoord5", "TexCoord6", "TexCoord7"
+};
+
+
+const VertexComponent s_UnityVertexComponents[kAttribLookupTableSize] = {
+ kVertexCompVertex,
+ kVertexCompColor,
+ kVertexCompNormal,
+ kVertexCompTexCoord,
+ kVertexCompTexCoord0, kVertexCompTexCoord1, kVertexCompTexCoord2, kVertexCompTexCoord3,
+ kVertexCompTexCoord4, kVertexCompTexCoord5, kVertexCompTexCoord6, kVertexCompTexCoord7
+};
+
+const int s_GLESVertexComponents[kAttribLookupTableSize] = {
+ kGLES3AttribLocationPosition,
+ kGLES3AttribLocationColor,
+ kGLES3AttribLocationNormal,
+ -1 /*kVertexCompTexCoord*/,
+ kGLES3AttribLocationTexCoord0,
+ kGLES3AttribLocationTexCoord1,
+ kGLES3AttribLocationTexCoord2,
+ kGLES3AttribLocationTexCoord3,
+ kGLES3AttribLocationTexCoord4,
+ kGLES3AttribLocationTexCoord5,
+ kGLES3AttribLocationTexCoord6,
+ kGLES3AttribLocationTexCoord7,
+};
+
+GlslGpuProgramGLES30::GlslGpuProgramGLES30 (const std::string& source, CreateGpuProgramOutput& output)
+{
+ output.SetPerFogModeParamsEnabled(true);
+ m_ImplType = kShaderImplBoth;
+ for (int i = 0; i < kFogModeCount; ++i)
+ {
+ m_GLSLVertexShader[i] = 0;
+ m_GLSLFragmentShader[i] = 0;
+ m_FogColorIndex[i] = -1;
+ m_FogParamsIndex[i] = -1;
+ m_FogFailed[i] = false;
+ }
+
+ // Fragment shaders come out as dummy GLSL text. Just ignore them; the real shader was part of
+ // the vertex shader text anyway.
+ if (source.empty())
+ return;
+
+ if (Create (source, output.CreateChannelAssigns()))
+ {
+ GpuProgramParameters& params = output.CreateParams ();
+ FillParams (m_Programs[kFogDisabled], params, output.GetOutNames());
+ if (params.GetTextureParams().size() > gGraphicsCaps.maxTexImageUnits)
+ m_NotSupported = true;
+
+ m_UniformCache[kFogDisabled].Create(&params, -1, -1);
+ }
+ else
+ {
+ m_NotSupported = true;
+ }
+}
+
+GlslGpuProgramGLES30::~GlslGpuProgramGLES30 ()
+{
+ Assert (m_ImplType == kShaderImplBoth);
+ for (int i = 0; i < kFogModeCount; ++i)
+ {
+ if (m_GLSLVertexShader[i]) { GLES_CHK(glDeleteShader(m_GLSLVertexShader[i])); }
+ if (m_GLSLFragmentShader[i]) { GLES_CHK(glDeleteShader(m_GLSLFragmentShader[i])); }
+ if (m_Programs[i]) { GLES_CHK(glDeleteProgram(m_Programs[i])); }
+ m_UniformCache[i].Destroy();
+ }
+}
+
+static bool ParseGlslErrors (GLuint type , GLSLErrorType errorType, const char* source = 0)
+{
+ bool hadErrors = false;
+ char compileInfoLog[4096];
+ GLsizei infoLogLength = 0;
+ switch(errorType)
+ {
+ case kErrorCompileVertexShader:
+ case kErrorCompileFragShader:
+ glGetShaderInfoLog(type, 4096, &infoLogLength, compileInfoLog );
+ break;
+ case kErrorLinkProgram:
+ glGetProgramInfoLog(type, 4096, &infoLogLength, compileInfoLog );
+ break;
+ default:
+ FatalErrorMsg("Unknown error type");
+ break;
+ }
+
+ // Make sure it is null-terminated
+ compileInfoLog[std::min<int>(infoLogLength, 4096)] = 0;
+
+ if (strlen (compileInfoLog) > 0)
+ {
+ hadErrors = true;
+ if (source)
+ {
+ printf_console("-------- GLSL source: \n");
+ DebugTextLineByLine(source);
+ }
+
+ printf_console("-------- GLSL error:\n%s\n\n", compileInfoLog);
+ }
+
+ return hadErrors;
+}
+
+static std::string ExtractDefineBock(const std::string& defineName, const std::string& str, std::string* remainderStr)
+{
+ const std::string beginsWith = "#ifdef " + defineName;
+ const std::string endsWith = "#endif";
+
+ size_t b;
+ if ((b = str.find(beginsWith))==std::string::npos)
+ return "";
+
+ b += beginsWith.size();
+
+ size_t e = b;
+ size_t n = 1;
+ do
+ {
+ size_t nextEnd = str.find(endsWith, e);
+ size_t nextIf = str.find("#if", e);
+
+ if (nextEnd == std::string::npos)
+ return "";
+
+ if (nextIf != std::string::npos && nextIf < nextEnd)
+ {
+ ++n;
+ e = nextIf + 1;
+ }
+ else
+ {
+ --n;
+ e = nextEnd + 1;
+ }
+ }
+ while (n > 0);
+
+ std::string retVal = str.substr(b, e-b-1);
+ if (remainderStr)
+ {
+ *remainderStr = str.substr(0, b - beginsWith.size());
+ if (e + endsWith.size() < str.length()) *remainderStr += str.substr(e + endsWith.size());
+ }
+
+ return retVal;
+}
+
+static void FindProgramStart(const char* source, std::string* header, std::string* prog)
+{
+ const char* line_start = source;
+ while(*line_start)
+ {
+ while(isspace(*line_start))
+ ++line_start;
+ if(*line_start == '#')
+ {
+ while(*line_start != '\n' && *line_start != '\r')
+ ++line_start;
+ }
+ else
+ {
+ header->assign(source, line_start - source);
+ prog->assign(line_start);
+ break;
+ }
+ }
+}
+
+bool GlslGpuProgramGLES30::CompileGlslShader(GLShaderID shader, const char* source)
+{
+ GLES_CHK(glShaderSource(shader, 1, &source, NULL));
+ GLES_CHK(glCompileShader(shader));
+
+ int compiled = 10;
+ GLES_CHK(glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled));
+
+ if (compiled==0)
+ {
+ ParseGlslErrors(shader, kErrorCompileFragShader, source);
+ return false;
+ }
+
+ return true;
+}
+
+bool GlslGpuProgramGLES30::Create (const std::string &shaderString, ChannelAssigns& channels)
+{
+ GLESAssert(); // Clear any GL errors
+
+ GLES_CHK(m_Programs[0] = glCreateProgram());
+
+ m_ImplType = kShaderImplBoth;
+
+ // NOTE: pre-pending shader with VERTEX/FRAGMENT defines doesn't work with ES/GLSL compilers for some reason
+ // therefore we extract VERTEX/FRAGMENT sections from the shaderString
+
+ std::string remainder = shaderString;
+ std::string vertexShaderSource = ExtractDefineBock("VERTEX", shaderString, &remainder);
+ std::string fragmentShaderSource = ExtractDefineBock("FRAGMENT", remainder, &remainder);
+
+ vertexShaderSource = remainder + vertexShaderSource;
+ fragmentShaderSource = remainder + fragmentShaderSource;
+
+ if(!CompileProgram(0, vertexShaderSource, fragmentShaderSource, channels))
+ {
+ ParseGlslErrors( m_Programs[0], kErrorLinkProgram );
+
+ int charsWritten=0;
+
+ if(m_GLSLVertexShader[0])
+ {
+ int vertShaderLength = 0;
+ GLES_CHK(glGetShaderiv(m_GLSLVertexShader[0], GL_SHADER_SOURCE_LENGTH, &vertShaderLength));
+ char* modVertexShader = new char[vertShaderLength];
+ GLES_CHK(glGetShaderSource(m_GLSLVertexShader[0], vertShaderLength, &charsWritten, modVertexShader));
+
+ #if UNITY_WIN
+ OutputDebugString(Format("Vertex Shader:\n%s\n", modVertexShader).c_str());
+ #else
+ ::printf_console("Vertex Shader:\n");
+ DebugTextLineByLine(modVertexShader);
+ #endif
+
+ delete[] modVertexShader;
+ }
+
+ if(m_GLSLFragmentShader[0])
+ {
+ int fragShaderLength = 0;
+ GLES_CHK(glGetShaderiv(m_GLSLFragmentShader[0], GL_SHADER_SOURCE_LENGTH, &fragShaderLength));
+ char* modFragmentShader = new char[fragShaderLength];
+ GLES_CHK(glGetShaderSource(m_GLSLFragmentShader[0], fragShaderLength, &charsWritten, modFragmentShader));
+
+ #if UNITY_WIN
+ OutputDebugString(Format("Fragment Shader:\n%s\n", modFragmentShader).c_str());
+ #else
+ ::printf_console("Vertex Shader:\n");
+ DebugTextLineByLine(modFragmentShader);
+ #endif
+
+ delete[] modFragmentShader;
+ }
+
+ // TODO: cleanup
+ return false;
+ }
+
+ m_VertexShaderSourceForFog = vertexShaderSource;
+ m_SourceForFog = fragmentShaderSource;
+
+ return true;
+}
+
+// \todo [2013-04-17 pyry] Uh...
+std::string GlslGpuProgramGLES30::_CachePath;
+
+bool GlslGpuProgramGLES30::InitBinaryShadersSupport()
+{
+ if (gGraphicsCaps.gles30.useProgramBinary)
+ {
+ int binFormatCount = 0;
+ GLES_CHK(glGetIntegerv(GL_NUM_PROGRAM_BINARY_FORMATS_OES, &binFormatCount));
+
+ if(binFormatCount > 0)
+ {
+ _CachePath = GetPersistentDataPathApplicationSpecific() + "/shader/";
+ if(!IsDirectoryCreated(_CachePath))
+ CreateDirectory(_CachePath);
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static void Internal_ClearShaderCache()
+{
+ std::string shaderCache = GetPersistentDataPathApplicationSpecific() + "/shader/";
+ DeleteFileOrDirectory(shaderCache);
+ CreateDirectory(shaderCache);
+}
+
+bool GlslGpuProgramGLES30::CompileProgram(unsigned index, const std::string& vprog, const std::string& fshader, ChannelAssigns& channels)
+{
+ std::string binaryPath;
+
+ if (gGraphicsCaps.gles30.useProgramBinary)
+ {
+ char filename[33] = {0};
+ GetCachedBinaryName(vprog, fshader, filename);
+
+ binaryPath = _CachePath + filename;
+ #if DEBUG_SHADER_CACHE
+ ::printf_console("Starting compilation \"%s\" variation with md5:%s\n", g_LastParsedShaderName.c_str(), filename);
+ #endif
+ }
+
+ // first 4 bytes are for binary format
+ char* binaryData = 0;
+ char* binaryProgram = 0;
+ unsigned binaryLength = 0;
+
+ bool loadedBinary = false;
+ if (gGraphicsCaps.gles30.useProgramBinary)
+ {
+ FILE* binaryFile = ::fopen(binaryPath.c_str(), "rb");
+ if(binaryFile)
+ {
+ ::fseek(binaryFile, 0, SEEK_END);
+ unsigned datasz = (unsigned)::ftell(binaryFile);
+ ::fseek(binaryFile, 0, SEEK_SET);
+
+ binaryData = (char*)::malloc(datasz);
+ binaryProgram = binaryData + sizeof(GLenum);
+ binaryLength = datasz - sizeof(GLenum);
+ ::fread(binaryData, datasz, 1, binaryFile);
+ ::fclose(binaryFile);
+
+ loadedBinary = true;
+ #if DEBUG_SHADER_CACHE
+ ::printf_console("Loaded from cache");
+ #endif
+ }
+ }
+
+ if(loadedBinary)
+ {
+ loadedBinary = false;
+
+ bool attrBound = index==0 ? BindVProgAttrbutes(vprog, channels, m_Programs[0])
+ : RebindVProgAttrbutes(m_Programs[index], m_Programs[0]);
+ if(attrBound)
+ {
+ GLES_CHK(glProgramBinary(m_Programs[index], *((GLenum*)binaryData), binaryProgram, binaryLength));
+
+ int linked = 0;
+ GLES_CHK(glGetProgramiv(m_Programs[index], GL_LINK_STATUS, &linked));
+ loadedBinary = linked != 0;
+ }
+
+ if(!loadedBinary)
+ {
+ #if DEBUG_SHADER_CACHE
+ ::printf_console("Bad cached version\n");
+ #endif
+
+ ::free(binaryData);
+ binaryProgram = binaryData = 0;
+ }
+ }
+
+ // fallback to compiling shaders at runtime
+ if(!loadedBinary)
+ {
+ DBG_SHADER_VERBOSE_GLES30("Compiling shader: %s\n", g_LastParsedShaderName.c_str());
+ #if DEBUG_SHADER_CACHE
+ ::printf_console("Actually compiling");
+ #endif
+
+ if(!CompileGLSLVertexShader(vprog, channels, m_Programs[index], m_Programs[0], &m_GLSLVertexShader[index]))
+ return false;
+ if(!CompileGLSLFragmentShader(fshader, &m_GLSLFragmentShader[index]))
+ return false;
+
+ GLES_CHK(glAttachShader(m_Programs[index], m_GLSLVertexShader[index]));
+ GLES_CHK(glAttachShader(m_Programs[index], m_GLSLFragmentShader[index]));
+ GLES_CHK(glLinkProgram(m_Programs[index]));
+
+ int linked = 0;
+ GLES_CHK(glGetProgramiv(m_Programs[index], GL_LINK_STATUS, &linked));
+ if(linked == 0)
+ return false;
+
+ if (gGraphicsCaps.gles30.useProgramBinary)
+ {
+ Assert(binaryData == 0 && binaryProgram == 0);
+
+ GLES_CHK(glGetProgramiv(m_Programs[index], GL_PROGRAM_BINARY_LENGTH_OES, (GLint*)&binaryLength));
+ binaryData = (char*)::malloc(binaryLength + sizeof(GLenum));
+ binaryProgram = binaryData + sizeof(GLenum);
+ GLES_CHK(glGetProgramBinary(m_Programs[index], binaryLength, 0, (GLenum*)binaryData, binaryProgram));
+
+ FILE* binaryFile = ::fopen(binaryPath.c_str(), "wb");
+ if(binaryFile)
+ {
+ ::fwrite(binaryData, binaryLength + sizeof(GLenum), 1, binaryFile);
+ ::fclose(binaryFile);
+
+ #if DEBUG_SHADER_CACHE
+ ::printf_console("Saved to cache\n");
+ #endif
+ }
+ }
+ }
+
+ if(binaryData)
+ ::free(binaryData);
+
+ return true;
+}
+
+
+
+static void PrintNumber (char* s, int i, bool brackets)
+{
+ DebugAssert (i >= 0 && i < 100);
+ if (brackets)
+ *s++ = '[';
+
+ if (i < 10) {
+ *s++ = '0' + i;
+ } else {
+ *s++ = '0' + i/10;
+ *s++ = '0' + i%10;
+ }
+
+ if (brackets)
+ *s++ = ']';
+ *s++ = 0;
+}
+
+static void AddSizedVectorParam (GpuProgramParameters& params, ShaderParamType type, GLuint program, int vectorSize, int uniformNumber, int arraySize, const char* unityName, char* glName, int glNameIndexOffset, PropertyNamesSet* outNames)
+{
+ if (arraySize <= 1)
+ {
+ params.AddVectorParam (uniformNumber, type, vectorSize, unityName, -1, outNames);
+ }
+ else
+ {
+ for (int j = 0; j < arraySize; ++j)
+ {
+ PrintNumber (glName+glNameIndexOffset, j, true);
+ uniformNumber = glGetUniformLocation (program, glName);
+ PrintNumber (glName+glNameIndexOffset, j, false);
+ params.AddVectorParam (uniformNumber, type, vectorSize, glName, -1, outNames);
+ }
+ }
+}
+
+static void AddSizedMatrixParam (GpuProgramParameters& params, GLuint program, int rows, int cols, int uniformNumber, int arraySize, const char* unityName, char* glName, int glNameIndexOffset, PropertyNamesSet* outNames)
+{
+ if (arraySize <= 1)
+ {
+ params.AddMatrixParam (uniformNumber, unityName, rows, cols, -1, outNames);
+ }
+ else
+ {
+ for (int j = 0; j < arraySize; ++j)
+ {
+ PrintNumber (glName+glNameIndexOffset, j, true);
+ uniformNumber = glGetUniformLocation (program, glName);
+ PrintNumber (glName+glNameIndexOffset, j, false);
+ params.AddMatrixParam (uniformNumber, glName, rows, cols, -1, outNames);
+ }
+ }
+}
+
+
+void GlslGpuProgramGLES30::FillParams (unsigned int programID, GpuProgramParameters& params, PropertyNamesSet* outNames)
+{
+ if (!programID)
+ return;
+
+ int activeUniforms;
+
+ char name[1024];
+ GLenum type;
+ int arraySize = 0,
+ nameLength = 0,
+ bufSize = sizeof(name);
+
+ DBG_LOG_GLES30("GLSL: apply params to program id=%i\n", programID);
+ GLSLUseProgramGLES30 (programID);
+
+ // Figure out the uniforms
+ GLES_CHK(glGetProgramiv (programID, GL_ACTIVE_UNIFORMS, &activeUniforms));
+ for(int i=0; i < activeUniforms; i++)
+ {
+ GLES_CHK(glGetActiveUniform (programID, i, bufSize, &nameLength, &arraySize, &type, name));
+
+ if (!strcmp (name, "_unity_FogParams") || !strcmp(name, "_unity_FogColor"))
+ continue;
+
+ // some Unity builtin properties are mapped to GLSL structure fields
+ // hijack them here
+ const char* glslName = GetGLSLES3PropertyNameRemap(name);
+ const char* unityName = glslName ? glslName : name;
+
+ if (!strncmp (name, "gl_", 3)) // skip "gl_" names
+ continue;
+
+ int uniformNumber = glGetUniformLocation (programID, name);
+ Assert(uniformNumber != -1);
+
+ char* glName = name;
+ int glNameIndexOffset = 0;
+
+ bool isElemZero = false;
+ bool isArray = IsShaderParameterArray(name, nameLength, arraySize, &isElemZero);
+ if (isArray)
+ {
+ // for array parameters, transform name a bit: Foo[0] becomes Foo0
+ if (arraySize >= 100) {
+ ErrorString( "GLSL: array sizes larger than 99 not supported" ); // TODO: SL error
+ arraySize = 99;
+ }
+ // TODO: wrong? what if we use only array[1] for example
+ if (isElemZero)
+ {
+ glNameIndexOffset = nameLength-3;
+ if(glNameIndexOffset < 0)
+ glNameIndexOffset = 0;
+ glName[glNameIndexOffset] = '0';
+ glName[glNameIndexOffset+1] = 0;
+ }
+ else
+ {
+ glNameIndexOffset = nameLength;
+ }
+ }
+
+ if(type == GL_FLOAT) {
+ AddSizedVectorParam (params, kShaderParamFloat, programID, 1, uniformNumber, arraySize, unityName, glName, glNameIndexOffset, outNames);
+ }
+ else if(type == GL_FLOAT_VEC2) {
+ AddSizedVectorParam (params, kShaderParamFloat, programID, 2, uniformNumber, arraySize, unityName, glName, glNameIndexOffset, outNames);
+ }
+ else if(type == GL_FLOAT_VEC3) {
+ AddSizedVectorParam (params, kShaderParamFloat, programID, 3, uniformNumber, arraySize, unityName, glName, glNameIndexOffset, outNames);
+ }
+ else if(type == GL_FLOAT_VEC4) {
+ AddSizedVectorParam (params, kShaderParamFloat, programID, 4, uniformNumber, arraySize, unityName, glName, glNameIndexOffset, outNames);
+ }
+ else if(type == GL_FLOAT_MAT4) {
+ AddSizedMatrixParam (params, programID, 4, 4, uniformNumber, arraySize, unityName, glName, glNameIndexOffset, outNames);
+ }
+ else if(type == GL_FLOAT_MAT3) {
+ AddSizedMatrixParam (params, programID, 3, 3, uniformNumber, arraySize, unityName, glName, glNameIndexOffset, outNames);
+ }
+
+ else if (type == GL_SAMPLER_2D || type == GL_SAMPLER_2D_SHADOW) {
+ GLES_CHK(glUniform1i (uniformNumber, params.GetTextureParams().size()));
+ params.AddTextureParam (uniformNumber, -1, unityName, kTexDim2D, outNames);
+ }
+ else if (type == GL_SAMPLER_CUBE || type == GL_SAMPLER_CUBE_SHADOW) {
+ GLES_CHK(glUniform1i (uniformNumber, params.GetTextureParams().size()));
+ params.AddTextureParam (uniformNumber, -1, unityName, kTexDimCUBE, outNames);
+ }
+ /*
+ else if(type == GL_SAMPLER_3D) {
+ GLES_CHK(glUniform1i (uniformNumber, params.GetTextureParams().size()));
+ params.AddTextureParam( name, kTexDim3D, uniformNumber );
+ }
+ else if(type == GL_SAMPLER_2D_SHADOW) {
+ GLES_CHK(glUniform1i (uniformNumber, params.GetTextureParams().size()));
+ params.AddTextureParam( name, kTexDim2D, uniformNumber );
+ }
+
+ */
+ else {
+ AssertString( "Unrecognized GLSL uniform type" );
+ }
+ }
+
+ GLESAssert();
+}
+
+static bool RebindVProgAttrbutes(GLShaderID programID, GLShaderID parentProgramID)
+{
+ int attribCount = 0;
+ GLES_CHK(glGetProgramiv(parentProgramID, GL_ACTIVE_ATTRIBUTES, &attribCount));
+
+ const int kBufSize = 256;
+ char name[kBufSize];
+ for(int i = 0 ; i < attribCount ; ++i)
+ {
+ int nameLength = 0, arraySize = 0;
+ GLenum type;
+ GLES_CHK(glGetActiveAttrib (parentProgramID, i, kBufSize, &nameLength, &arraySize, &type, name));
+ int location = glGetAttribLocation (parentProgramID, name);
+ if (location != -1)
+ GLES_CHK(glBindAttribLocation(programID, location, name));
+ }
+
+ return true;
+}
+
+static bool BindVProgAttrbutes(const std::string& source, ChannelAssigns& channels, GLShaderID programID)
+{
+ // Add necessary attribute tags
+ for (UInt32 j = 0; j < kAttribLookupTableSize; j++)
+ {
+ if (source.find(s_GLSLESAttributes[j]) != std::string::npos)
+ {
+ if (s_GLESVertexComponents[j] >= gGraphicsCaps.gles30.maxAttributes)
+ {
+ ErrorString("Shader uses too many vertex attributes for this platform");
+ return false;
+ }
+ GLES_CHK(glBindAttribLocation(programID, s_GLESVertexComponents[j], s_GLSLESAttributes[j]));
+ ShaderChannel shaderChannel = GetShaderChannelFromName(s_UnityAttributes[j]);
+ if( shaderChannel != kShaderChannelNone )
+ channels.Bind (shaderChannel, s_UnityVertexComponents[j]);
+ }
+ }
+
+ // UGLY HACK:
+ // somewhere deep inside shader generation we just put attribute TANGENT
+ // without ever using it after
+ // look for TANGENT twice to be sure we use it
+ size_t firstTangentUsage = source.find("TANGENT");
+ if( firstTangentUsage != std::string::npos && source.find("TANGENT", firstTangentUsage+1) != std::string::npos )
+ {
+ // Find first free slot for tangents and use it.
+ // Unity normally supports 2 UV slots (0&1), so start looking from
+ // kVertexTexCoord2
+ for (int i = kVertexCompTexCoord2; i < kVertexCompTexCoord7; i++)
+ {
+ if (channels.GetSourceForTarget((VertexComponent)i) == kShaderChannelNone)
+ {
+ channels.Bind (kShaderChannelTangent, (VertexComponent)i);
+ GLES_CHK(glBindAttribLocation(programID, kGLES3AttribLocationTexCoord0 + i - kVertexCompTexCoord0, "_glesTANGENT"));
+ break;
+ }
+ }
+ }
+
+ return true;
+}
+
+static bool CompileGLSLVertexShader (const std::string& source, ChannelAssigns& channels, GLShaderID programID, GLShaderID parentProgramID, GLShaderID* outShaderID)
+{
+ GLES_CHK(*outShaderID = glCreateShader(GL_VERTEX_SHADER));
+
+ if(parentProgramID == programID)
+ BindVProgAttrbutes(source, channels, programID);
+ else
+ RebindVProgAttrbutes(programID, parentProgramID);
+
+ const char* text = source.c_str();
+ DBG_SHADER_VERBOSE_GLES30_DUMP_SHADER("Compiling VERTEX program:", text);
+
+ if (GlslGpuProgramGLES30::CompileGlslShader(*outShaderID, text))
+ return true;
+
+ printf_console ("GLES30: failed to compile vertex shader:\n%s\n", text);
+
+ // TODO: cleanup
+ return false;
+}
+
+static bool CompileGLSLFragmentShader (const std::string& source, GLShaderID* outShaderID)
+{
+ *outShaderID = glCreateShader(GL_FRAGMENT_SHADER);
+
+ ///@TODO: find any existing precision statement
+
+ std::string modSourceHeader, modSourceProg;
+ FindProgramStart(source.c_str(), &modSourceHeader, &modSourceProg);
+
+ // \todo [2013-04-17 pyry] Should we default to highp or mediump?
+ std::string modSource = modSourceHeader
+ + std::string("precision highp float;\n")
+ + modSourceProg;
+
+ const char* text = modSource.c_str();
+ DBG_SHADER_VERBOSE_GLES30_DUMP_SHADER("Compiling FRAGMENT program:", text);
+
+ if (GlslGpuProgramGLES30::CompileGlslShader(*outShaderID, text))
+ return true;
+
+ printf_console ("GLES30: failed to compile fragment shader:\n%s\n", text);
+
+ // TODO: cleanup
+ return false;
+}
+
+int GlslGpuProgramGLES30::GetGLProgram (FogMode fog, GpuProgramParameters& outParams, ChannelAssigns &channels)
+{
+ int index = 0;
+ if (fog > kFogDisabled && !m_FogFailed[fog] && !m_SourceForFog.empty())
+ {
+ index = fog;
+ Assert (index >= 0 && index < kFogModeCount);
+
+ // create patched fog program if needed
+ if(!m_Programs[index])
+ {
+ std::string srcVS = m_VertexShaderSourceForFog;
+ std::string srcPS = m_SourceForFog;
+
+ if(PatchShaderFogGLES (srcVS, srcPS, fog, CanUseOptimizedFogCodeGLES(srcVS)))
+ {
+ // create program, shaders, link
+ GLES_CHK(m_Programs[index] = glCreateProgram());
+ ShaderErrors errors;
+ if(CompileProgram(index, srcVS, srcPS, channels))
+ {
+ FillParams (m_Programs[index], outParams, NULL);
+ m_FogParamsIndex[index] = glGetUniformLocation(m_Programs[index], "_unity_FogParams");
+ m_FogColorIndex[index] = glGetUniformLocation(m_Programs[index], "_unity_FogColor");
+
+ m_UniformCache[index].Create(&outParams, m_FogParamsIndex[index], m_FogColorIndex[index]);
+ }
+ else
+ {
+ if(m_GLSLVertexShader[index])
+ glDeleteShader(m_GLSLVertexShader[index]);
+
+ if(m_GLSLFragmentShader[index])
+ glDeleteShader(m_GLSLFragmentShader[index]);
+
+ m_GLSLVertexShader[index] = 0;
+ m_GLSLFragmentShader[index] = 0;
+
+ glDeleteProgram(m_Programs[index]);
+ m_Programs[index] = 0;
+
+ m_FogFailed[index] = true;
+ index = 0;
+ }
+ }
+ else
+ {
+ m_FogFailed[index] = true;
+ index = 0;
+ }
+ }
+ }
+ return index;
+}
+
+int GlslGpuProgramGLES30::ApplyGpuProgramES30 (const GpuProgramParameters& params, const UInt8 *buffer)
+{
+ DBG_LOG_GLES30("GlslGpuProgramGLES30::ApplyGpuProgramES30()");
+
+ // m_Programs[0] == 0, is when Unity tries to build a dummy shader with empty source for fragment shaders, do nothing in this case
+ if (m_Programs[0] == 0)
+ return 0;
+
+ GfxDevice& device = GetRealGfxDevice();
+
+ const GfxFogParams& fog = device.GetFogParams();
+ const int index = (int)fog.mode;
+
+ DBG_LOG_GLES30("GLSL: apply program id=%i\n", m_Programs[index]);
+ GLSLUseProgramGLES30 (m_Programs[index]);
+
+ // Apply value parameters
+ const GpuProgramParameters::ValueParameterArray& valueParams = params.GetValueParams();
+ GpuProgramParameters::ValueParameterArray::const_iterator valueParamsEnd = valueParams.end();
+ for( GpuProgramParameters::ValueParameterArray::const_iterator i = valueParams.begin(); i != valueParamsEnd; ++i )
+ {
+ if (i->m_RowCount == 1 && i->m_ArraySize == 1)
+ {
+ UniformCacheGLES30* cache = m_UniformCache+index;
+// const float* val = i->m_ColCount == 1 ? &ShaderLab::shaderprops::GetFloat(props, i->m_Name)
+// : ShaderLab::shaderprops::GetVector(props, i->m_Name).GetPtr();
+ const float * val = reinterpret_cast<const float*>(buffer);
+
+ switch (i->m_ColCount)
+ {
+ case 1: CachedUniform1(cache, i->m_Index, val); break;
+ case 2: CachedUniform2(cache, i->m_Index, val); break;
+ case 3: CachedUniform3(cache, i->m_Index, val); break;
+ case 4: CachedUniform4(cache, i->m_Index, val); break;
+ default: break;
+ }
+
+ #if DEBUG_GLSL_BINDINGS
+ ;;printf_console(" vector %i dim=%i\n", i->m_Index, i->m_Dim );
+ #endif
+
+ buffer += 4*sizeof(float);
+ }
+ else
+ {
+ // Apply matrix parameters
+ DebugAssert (i->m_ArraySize == 1);
+ int size = *reinterpret_cast<const int*>(buffer); buffer += sizeof(int);
+ Assert (size == 16);
+ const Matrix4x4f* mat = reinterpret_cast<const Matrix4x4f*>(buffer);
+ if (i->m_RowCount == 3 && i->m_ColCount == 3)
+ {
+ Matrix3x3f m33 = Matrix3x3f(*mat);
+ GLES_CHK(glUniformMatrix3fv (i->m_Index, 1, GL_FALSE, m33.GetPtr()));
+ }
+ else
+ {
+ const float *ptr = mat->GetPtr ();
+ GLES_CHK(glUniformMatrix4fv (i->m_Index, 1, GL_FALSE, ptr));
+ }
+ #if DEBUG_GLSL_BINDINGS
+ ;;printf_console(" matrix %i (%s)\n", i->m_Index, i->m_Name.GetName() );
+ #endif
+ buffer += size * sizeof(float);
+ }
+ }
+
+ // Apply textures
+ int j = 0;
+ const GpuProgramParameters::TextureParameterList& textureParams = params.GetTextureParams();
+ GpuProgramParameters::TextureParameterList::const_iterator textureParamsEnd = textureParams.end();
+ for( GpuProgramParameters::TextureParameterList::const_iterator i = textureParams.begin(); i != textureParamsEnd; ++i )
+ {
+ const TexEnvData* texdata = reinterpret_cast<const TexEnvData*>(buffer);
+ #if DEBUG_GLSL_BINDINGS
+ ;;printf_console(" sampler %i to unit %i (%s) id=%i dim=%i\n", t.m_Index, j, t.m_Name.GetName(), tex->GetActualTextureID().m_ID, tex->GetTexDim() );
+ #endif
+ ApplyTexEnvData (j, j, *texdata);
+ ++j;
+ buffer += sizeof(TexEnvData);
+ }
+
+ // Fog parameters if needed
+ if (index > 0)
+ {
+ if (m_FogColorIndex[fog.mode] >= 0)
+ CachedUniform4(m_UniformCache+index, m_FogColorIndex[fog.mode], fog.color.GetPtr());
+
+ Vector4f params(
+ fog.density * 1.2011224087f,// density / sqrt(ln(2))
+ fog.density * 1.4426950408f, // density / ln(2)
+ 0.0f,
+ 0.0f
+ );
+
+ if (fog.mode == kFogLinear)
+ {
+ float diff = fog.end - fog.start;
+ float invDiff = Abs(diff) > 0.0001f ? 1.0f/diff : 0.0f;
+ params[2] = -invDiff;
+ params[3] = fog.end * invDiff;
+ }
+
+ if (m_FogParamsIndex[fog.mode] >= 0)
+ CachedUniform4(m_UniformCache+index, m_FogParamsIndex[fog.mode], params.GetPtr());
+ }
+
+ GLESAssert();
+
+ return index;
+}
+
+
+// --------------------------------------------------------------------------
+
+const ShaderLab::FastPropertyName FixedFunctionProgramGLES30::kSLPropTransformBlock = ShaderLab::Property("_ffTransform");
+const ShaderLab::FastPropertyName FixedFunctionProgramGLES30::kSLPropUVTransformBlock = ShaderLab::Property("_ffUVTransform");
+
+
+FixedFunctionProgramGLES30::FixedFunctionProgramGLES30(GLShaderID vertexShader, GLShaderID fragmentShader)
+: m_GLSLProgram(0)
+, m_GLSLVertexShader(vertexShader)
+, m_GLSLFragmentShader(fragmentShader)
+{
+ m_GLSLProgram = Create(m_GLSLVertexShader, m_GLSLFragmentShader);
+}
+
+FixedFunctionProgramGLES30::~FixedFunctionProgramGLES30 ()
+{
+ // NOTE: do not delete vertex/fragment shaders; they can be shared between multiple programs
+ // and are deleted in ClearFixedFunctionPrograms
+ if (m_GLSLProgram != 0 ) // only delete valid programs
+ GLES_CHK(glDeleteProgram(m_GLSLProgram));
+
+ m_UniformCache.Destroy();
+}
+
+GLShaderID FixedFunctionProgramGLES30::Create(GLShaderID vertexShader, GLShaderID fragmentShader)
+{
+ GLESAssert(); // Clear any GL errors
+
+ GLuint program;
+ GLES_CHK(program = glCreateProgram());
+
+ for (int i = 0; i < kAttribLookupTableSize; i++)
+ {
+ if (s_GLESVertexComponents[i] < gGraphicsCaps.gles30.maxAttributes && s_GLESVertexComponents[i] >= 0)
+ GLES_CHK(glBindAttribLocation(program, s_GLESVertexComponents[i], s_GLSLESAttributes[i]));
+ }
+
+ GLES_CHK(glAttachShader(program, m_GLSLVertexShader));
+ GLES_CHK(glAttachShader(program, m_GLSLFragmentShader));
+
+ //We must link only after binding the attributes
+ GLES_CHK(glLinkProgram(program));
+
+ int linked = 0;
+ GLES_CHK(glGetProgramiv(program, GL_LINK_STATUS, &linked));
+ if (linked == 0)
+ {
+ ParseGlslErrors(program, kErrorLinkProgram);
+ GLES_CHK(glDeleteProgram(program));
+ return 0;
+ }
+
+ // Figure out the uniforms
+ int activeUniforms;
+ int activeUniformBlocks;
+
+ char name[1024];
+ GLenum type;
+ int size = 0,
+ length = 0,
+ bufSize = sizeof(name);
+
+ // Fetch texture stage samplers. Bind GLSL uniform to the OpenGL texture unit
+ // just once, cause it never changes after that for this program; and could cause
+ // internal shader recompiles when changing them.
+ DBG_LOG_GLES30("GLSL: apply fixed-function program id=%i\n", program);
+ GLSLUseProgramGLES30 (program);
+
+ for (int i = 0; i < kMaxSupportedTextureUnitsGLES; i++)
+ {
+ std::string samplerName = Format("u_sampler%d", i);
+ GLint uniformNumber;
+ GLES_CHK(uniformNumber = glGetUniformLocation(program, samplerName.c_str()));
+
+ if (uniformNumber != -1)
+ {
+ GLES_CHK(glUniform1i (uniformNumber, i));
+ DBG_GLSL_BINDINGS_GLES30(" FFsampler: %s nr=%d", samplerName.c_str(), uniformNumber);
+ }
+ }
+
+ // uniform blocks
+ GLES_CHK(glGetProgramiv(program, GL_ACTIVE_UNIFORM_BLOCKS, &activeUniformBlocks));
+ m_Params.GetConstantBuffers().clear();
+ m_Params.GetConstantBuffers().reserve(activeUniformBlocks);
+ for (int i = 0; i < activeUniformBlocks; ++i)
+ {
+ GLES_CHK(glGetActiveUniformBlockName(program, i, bufSize, &length, name));
+ GLES_CHK(glGetActiveUniformBlockiv(program, i, GL_UNIFORM_BLOCK_DATA_SIZE, &size));
+
+ GpuProgramParameters::ConstantBuffer cb;
+ cb.m_Name = ShaderLab::Property(name);
+ cb.m_Size = size;
+ cb.m_BindIndex = i;
+
+ GLES_CHK(glUniformBlockBinding(program, i, i));
+
+ m_Params.GetConstantBuffers().push_back(cb);
+ GetRealGfxDevice().SetConstantBufferInfo(cb.m_Name.index, cb.m_Size);
+ }
+
+ // fetch generic params
+ GLES_CHK(glGetProgramiv(program, GL_ACTIVE_UNIFORMS, &activeUniforms));
+ for(int i=0; i < activeUniforms; i++)
+ {
+ int uniformNumber;
+ int cbIndex = -1;
+
+ GLES_CHK(glGetActiveUniform(program, i, bufSize, &length, &size, &type, name));
+ GLES_CHK(uniformNumber = glGetUniformLocation(program, name));
+
+ const char* glslName = GetGLSLES3PropertyNameRemap(name);
+ const char* unityName = glslName ? glslName : name;
+
+ if (uniformNumber == GL_INVALID_INDEX)
+ {
+ // in a uniform block
+ GLES_CHK(glGetActiveUniformsiv(program, 1, (GLuint*)&i, GL_UNIFORM_OFFSET, &uniformNumber));
+ GLES_CHK(glGetActiveUniformsiv(program, 1, (GLuint*)&i, GL_UNIFORM_BLOCK_INDEX, &cbIndex));
+ }
+
+ if(type == GL_FLOAT) {
+ m_Params.AddVectorParam(uniformNumber, kShaderParamFloat, 1, unityName, cbIndex, NULL);
+ }
+ else if(type == GL_FLOAT_VEC2) {
+ m_Params.AddVectorParam(uniformNumber, kShaderParamFloat, 2, unityName, cbIndex, NULL);
+ }
+ else if(type == GL_FLOAT_VEC3) {
+ m_Params.AddVectorParam(uniformNumber, kShaderParamFloat, 3, unityName, cbIndex, NULL);
+ }
+ else if(type == GL_FLOAT_VEC4) {
+ m_Params.AddVectorParam(uniformNumber, kShaderParamFloat, 4, unityName, cbIndex, NULL);
+ }
+ else if(type == GL_FLOAT_MAT4) {
+ m_Params.AddMatrixParam(uniformNumber, unityName, 4, 4, cbIndex, NULL);
+ }
+ else if(type == GL_FLOAT_MAT3) {
+ m_Params.AddMatrixParam(uniformNumber, unityName, 3, 3, cbIndex, NULL);
+ }
+ else if(type == GL_SAMPLER_2D || type == GL_SAMPLER_CUBE) {
+ }
+ else {
+ AssertString( "Unrecognized GLSL uniform type" );
+ }
+ DBG_GLSL_BINDINGS_GLES30(" FFuniform: %s nr=%d type=%d", unityName, uniformNumber, type);
+ }
+
+ m_UniformCache.Create(&m_Params, -1,-1);
+
+ GLESAssert();
+ return program;
+}
+
+void FixedFunctionProgramGLES30::ApplyFFGpuProgram(const BuiltinShaderParamValues& values, ConstantBuffersGLES30& cbs) const
+{
+ DBG_LOG_GLES30("FixedFunctionProgramGLES30::ApplyFFGpuProgram()");
+
+ DBG_LOG_GLES30("GLSL: apply fixed-function program id=%i\n", m_GLSLProgram);
+ GLSLUseProgramGLES30 (m_GLSLProgram);
+
+ cbs.ResetBinds();
+
+ // Apply float/vector parameters
+ const GpuProgramParameters::ValueParameterArray& valueParams = m_Params.GetValueParams();
+ GpuProgramParameters::ValueParameterArray::const_iterator valueParamsEnd = valueParams.end();
+ for( GpuProgramParameters::ValueParameterArray::const_iterator i = valueParams.begin(); i != valueParamsEnd; ++i )
+ {
+ if(i->m_RowCount == 1 && i->m_ArraySize == 1)
+ {
+ UniformCacheGLES30* cache = &m_UniformCache;
+ const float * val = values.GetVectorParam((BuiltinShaderVectorParam)i->m_Name.BuiltinIndex()).GetPtr();
+
+ switch (i->m_ColCount)
+ {
+ case 1: CachedUniform1(cache, i->m_Index, val); break;
+ case 2: CachedUniform2(cache, i->m_Index, val); break;
+ case 3: CachedUniform3(cache, i->m_Index, val); break;
+ case 4: CachedUniform4(cache, i->m_Index, val); break;
+ default: break;
+ }
+ }
+ else
+ {
+ // Apply matrix parameters
+ DebugAssert (i->m_ArraySize == 1);
+ int matSize;
+ const Matrix4x4f& mat = values.GetMatrixParam((BuiltinShaderMatrixParam)i->m_Name.BuiltinIndex());
+ if (i->m_RowCount == 3 && i->m_ColCount == 3)
+ {
+ Matrix3x3f m33 = Matrix3x3f(mat);
+ GLES_CHK(glUniformMatrix3fv (i->m_Index, 1, GL_FALSE, m33.GetPtr()));
+ }
+ else
+ {
+ const float *ptr = mat.GetPtr ();
+ GLES_CHK(glUniformMatrix4fv (i->m_Index, 1, GL_FALSE, ptr));
+ }
+ DBG_GLSL_BINDINGS_GLES30(" FFmatrix %i (%s)", i->m_Index, i->m_Name.GetName() );
+ }
+ }
+
+ GLESAssert();
+}
+
+
+#if UNITY_ANDROID || UNITY_BLACKBERRY
+//-----------------------------------------------------------------------------
+// md5 internals are extracted from External/MurmurHash/md5.cpp
+ struct
+ md5_context
+ {
+ unsigned long total[2];
+ unsigned long state[4];
+ unsigned char buffer[64];
+ unsigned char ipad[64];
+ unsigned char opad[64];
+ };
+
+ extern void md5_starts(md5_context* ctx);
+ extern void md5_update(md5_context* ctx, unsigned char* input, int ilen);
+ extern void md5_finish(md5_context* ctx, unsigned char output[16]);
+#endif
+
+
+static void GetCachedBinaryName(const std::string& vprog, const std::string& fshader, char filename[33])
+{
+// we have caching only on android, so no need to worry (at least for now) about md5
+#if UNITY_ANDROID || UNITY_BLACKBERRY
+
+ unsigned char hash[16] = {0};
+
+ md5_context ctx;
+ md5_starts(&ctx);
+ md5_update(&ctx, (unsigned char*)vprog.c_str(), vprog.length());
+ md5_update(&ctx, (unsigned char*)fshader.c_str(), fshader.length());
+ md5_finish(&ctx, hash);
+
+ BytesToHexString(hash, 16, filename);
+
+#else
+
+ (void)vprog;
+ (void)fshader;
+ ::memset(filename, '1', 33);
+
+#endif
+}
+
+
+#endif // GFX_SUPPORTS_OPENGLES30
diff --git a/Runtime/GfxDevice/opengles30/GpuProgramsGLES30.h b/Runtime/GfxDevice/opengles30/GpuProgramsGLES30.h
new file mode 100644
index 0000000..73bd469
--- /dev/null
+++ b/Runtime/GfxDevice/opengles30/GpuProgramsGLES30.h
@@ -0,0 +1,88 @@
+#ifndef GPUPROGRAMSGLES30_H
+#define GPUPROGRAMSGLES30_H
+
+
+#if !GFX_SUPPORTS_OPENGLES30 && !GFX_SUPPORTS_OPENGLES
+#error "Should not include GpuProgramsGLES30 on this platform"
+#endif
+
+
+#include "Runtime/GfxDevice/GpuProgram.h"
+#include "Runtime/GfxDevice/GfxDeviceTypes.h"
+#include "Runtime/GfxDevice/ChannelAssigns.h"
+#include "IncludesGLES30.h"
+#include "GpuProgramsGLES30_UniformCache.h"
+
+class ConstantBuffersGLES30;
+
+class GlslGpuProgramGLES30 : public GpuProgramGL
+{
+public:
+ GlslGpuProgramGLES30 (const std::string& source, CreateGpuProgramOutput& output);
+
+ ~GlslGpuProgramGLES30();
+
+ virtual void ApplyGpuProgram (const GpuProgramParameters& params, const UInt8* buffer) { Assert(!"Should not be used"); }
+
+ // Returns the permutation index used
+ int ApplyGpuProgramES30 (const GpuProgramParameters& params, const UInt8 *buffer);
+
+ static bool InitBinaryShadersSupport();
+
+
+ UniformCacheGLES30 m_UniformCache[kFogModeCount];
+
+ static bool CompileGlslShader(GLShaderID shader, const char* source);
+ int GetGLProgram (FogMode fog, GpuProgramParameters& outParams, ChannelAssigns& channels);
+
+private:
+
+ static void FillParams (unsigned int programID, GpuProgramParameters& params, PropertyNamesSet* outNames);
+
+ bool Create (const std::string& source, ChannelAssigns& channels);
+
+ bool CompileProgram(unsigned index, const std::string& vprog, const std::string& fshader, ChannelAssigns& channels);
+
+private:
+ std::string m_VertexShaderSourceForFog;
+ GLShaderID m_GLSLVertexShader[kFogModeCount];
+ GLShaderID m_GLSLFragmentShader[kFogModeCount];
+ int m_FogColorIndex[kFogModeCount];
+ int m_FogParamsIndex[kFogModeCount];
+ bool m_FogFailed[kFogModeCount];
+
+ static std::string _CachePath;
+ static glGetProgramBinaryOESFunc _glGetProgramBinaryOES;
+ static glProgramBinaryOESFunc _glProgramBinaryOES;
+};
+
+
+class FixedFunctionProgramGLES30
+{
+public:
+ static const ShaderLab::FastPropertyName kSLPropTransformBlock;
+ static const ShaderLab::FastPropertyName kSLPropUVTransformBlock;
+
+public:
+ FixedFunctionProgramGLES30(GLShaderID vertexShader, GLShaderID fragmentShader);
+ ~FixedFunctionProgramGLES30();
+
+ void ApplyFFGpuProgram(const BuiltinShaderParamValues& values, ConstantBuffersGLES30& cbs) const;
+
+ const BuiltinShaderParamIndices& GetBuiltinParams() const { return m_Params.GetBuiltinParams(); }
+ const GpuProgramParameters::ConstantBufferList& GetConstantBuffers() const { return m_Params.GetConstantBuffers(); }
+
+ mutable UniformCacheGLES30 m_UniformCache;
+
+protected:
+ GLShaderID Create(GLShaderID vertexShader, GLShaderID fragmentShader);
+
+private:
+ GLShaderID m_GLSLProgram;
+ GLShaderID m_GLSLVertexShader, m_GLSLFragmentShader;
+
+ GpuProgramParameters m_Params;
+};
+
+
+#endif
diff --git a/Runtime/GfxDevice/opengles30/GpuProgramsGLES30_UniformCache.cpp b/Runtime/GfxDevice/opengles30/GpuProgramsGLES30_UniformCache.cpp
new file mode 100644
index 0000000..235ff7a
--- /dev/null
+++ b/Runtime/GfxDevice/opengles30/GpuProgramsGLES30_UniformCache.cpp
@@ -0,0 +1,62 @@
+#include "UnityPrefix.h"
+#if !GFX_SUPPORTS_OPENGLES30
+#error "Should not include GpuProgramsGLES30 on this platform"
+#endif
+
+#include "GpuProgramsGLES30_UniformCache.h"
+
+#include "Runtime/Allocator/MemoryMacros.h"
+#include "Runtime/GfxDevice/GpuProgram.h"
+#include "IncludesGLES30.h"
+#include "AssertGLES30.h"
+
+
+void UniformCacheGLES30::Create(const GpuProgramParameters* params, int fogParamsIndex, int fogColorIndex)
+{
+ int lastUsedUniform = -1;
+
+ // we will track only float/vector uniforms
+ GpuProgramParameters::ValueParameterArray::const_iterator paramI = params->GetValueParams().begin();
+ GpuProgramParameters::ValueParameterArray::const_iterator paramEnd = params->GetValueParams().end();
+ while(paramI != paramEnd)
+ {
+ if(paramI->m_RowCount == 1 && paramI->m_ArraySize == 1 && paramI->m_Index > lastUsedUniform)
+ lastUsedUniform = paramI->m_Index;
+
+ ++paramI;
+ }
+
+ const BuiltinShaderParamIndices& builtinParam = params->GetBuiltinParams();
+ for(unsigned i = 0 ; i < kShaderInstanceVecCount ; ++i)
+ {
+ if(builtinParam.vec[i].gpuIndex > lastUsedUniform)
+ lastUsedUniform = builtinParam.vec[i].gpuIndex;
+ }
+
+ if(fogParamsIndex > lastUsedUniform) lastUsedUniform = fogParamsIndex;
+ if(fogColorIndex > lastUsedUniform) lastUsedUniform = fogColorIndex;
+
+ count = lastUsedUniform + 1;
+ uniform = (float*)UNITY_MALLOC_ALIGNED(kMemShader, count*4 * sizeof(float), 16);
+ memset(uniform, 0xff /* NaN */, count*4 * sizeof(float));
+}
+
+void UniformCacheGLES30::Destroy()
+{
+ count = 0;
+
+ UNITY_FREE(kMemShader, uniform);
+ uniform = 0;
+}
+
+#define CACHED_UNIFORM_IMPL(Count) \
+void CachedUniform##Count(UniformCacheGLES30* cache, int index, const float* val) \
+{ \
+ if(cache->UpdateUniform(index, val, Count)) \
+ GLES_CHK(glUniform##Count##fv(index, 1, val)); \
+} \
+
+CACHED_UNIFORM_IMPL(1);
+CACHED_UNIFORM_IMPL(2);
+CACHED_UNIFORM_IMPL(3);
+CACHED_UNIFORM_IMPL(4);
diff --git a/Runtime/GfxDevice/opengles30/GpuProgramsGLES30_UniformCache.h b/Runtime/GfxDevice/opengles30/GpuProgramsGLES30_UniformCache.h
new file mode 100644
index 0000000..aa0e74b
--- /dev/null
+++ b/Runtime/GfxDevice/opengles30/GpuProgramsGLES30_UniformCache.h
@@ -0,0 +1,50 @@
+#pragma once
+
+#if !GFX_SUPPORTS_OPENGLES30
+#error "Should not include GpuProgramsGLES30 on this platform"
+#endif
+
+#include "Runtime/Utilities/LogAssert.h"
+#include <string.h>
+
+class GpuProgramParameters;
+
+struct
+UniformCacheGLES30
+{
+ // for gles we must set values per-uniform (not per-registers like in dx)
+ // so there is no real need for dirty tracking.
+ // TODO: do unified impl with dirty tracking if/when we do "everything is an array" in gles
+ float* uniform;
+ unsigned count;
+
+ UniformCacheGLES30() : uniform(0), count(0) {}
+
+ // we will pre-alloc memory. Fog params are handled differently (not added to gpu params).
+ // TODO: make it more general, int* perhaps, or some struct
+ void Create(const GpuProgramParameters* params, int fogParamsIndex, int fogColorIndex);
+ void Destroy();
+
+ // returns true if you need to update for real
+ bool UpdateUniform(int index, const float* val, unsigned floatCount);
+};
+
+void CachedUniform1(UniformCacheGLES30* cache, int index, const float* val);
+void CachedUniform2(UniformCacheGLES30* cache, int index, const float* val);
+void CachedUniform3(UniformCacheGLES30* cache, int index, const float* val);
+void CachedUniform4(UniformCacheGLES30* cache, int index, const float* val);
+
+
+inline bool UniformCacheGLES30::UpdateUniform(int index, const float* val, unsigned floatCount)
+{
+ Assert(index < count);
+ const unsigned mem_sz = floatCount*sizeof(float);
+
+ float* target = uniform + 4*index;
+ if(::memcmp(target, val, mem_sz))
+ {
+ ::memcpy(target, val, mem_sz);
+ return true;
+ }
+ return false;
+}
diff --git a/Runtime/GfxDevice/opengles30/GpuPropertiesGLES30.cpp b/Runtime/GfxDevice/opengles30/GpuPropertiesGLES30.cpp
new file mode 100644
index 0000000..ab5c8a2
--- /dev/null
+++ b/Runtime/GfxDevice/opengles30/GpuPropertiesGLES30.cpp
@@ -0,0 +1,70 @@
+#include "UnityPrefix.h"
+#include "GpuPropertiesGLES30.h"
+#include "Runtime/Utilities/ArrayUtility.h"
+#include "Runtime/GfxDevice/BuiltinShaderParams.h"
+#include "Runtime/GfxDevice/BuiltinShaderParamsNames.h"
+
+#if GFX_SUPPORTS_OPENGLES30
+
+struct GLSLESProperty
+{
+ GLSLESProperty(const char* _glName, const char* _glesName) : glName(_glName), unityName(_glesName) { }
+ const char* glName;
+ const char* unityName;
+};
+
+#define DEF_MAT_INTERNAL(name, builtin) GLSLESProperty(name, GetShaderInstanceMatrixParamName(builtin))
+#define DEF_MAT_BUILTIN(name, builtin) GLSLESProperty(name, GetBuiltinMatrixParamName(builtin))
+#define BIND_VEC_BUILTIN(name, builtin) GLSLESProperty(name, GetBuiltinVectorParamName(builtin))
+
+
+static const GLSLESProperty kglslesProperties[] =
+{
+ DEF_MAT_BUILTIN("gl_ProjectionMatrix", kShaderMatProj),
+ DEF_MAT_INTERNAL("gl_NormalMatrix", kShaderInstanceMatNormalMatrix),
+ DEF_MAT_INTERNAL("gl_ModelViewProjectionMatrix", kShaderInstanceMatMVP),
+ DEF_MAT_INTERNAL("gl_ModelViewMatrixTranspose", kShaderInstanceMatTransMV),
+ DEF_MAT_INTERNAL("gl_ModelViewMatrixInverseTranspose", kShaderInstanceMatInvTransMV),
+ DEF_MAT_INTERNAL("gl_ModelViewMatrix", kShaderInstanceMatMV),
+
+ DEF_MAT_INTERNAL("gl_TextureMatrix0", kShaderInstanceMatTexture0),
+ DEF_MAT_INTERNAL("gl_TextureMatrix1", kShaderInstanceMatTexture1),
+ DEF_MAT_INTERNAL("gl_TextureMatrix2", kShaderInstanceMatTexture2),
+ DEF_MAT_INTERNAL("gl_TextureMatrix3", kShaderInstanceMatTexture3),
+ DEF_MAT_INTERNAL("gl_TextureMatrix4", kShaderInstanceMatTexture4),
+ DEF_MAT_INTERNAL("gl_TextureMatrix5", kShaderInstanceMatTexture5),
+ DEF_MAT_INTERNAL("gl_TextureMatrix6", kShaderInstanceMatTexture6),
+ DEF_MAT_INTERNAL("gl_TextureMatrix7", kShaderInstanceMatTexture7),
+
+ BIND_VEC_BUILTIN("_glesLightSource[0].diffuse", kShaderVecLight0Diffuse),
+ BIND_VEC_BUILTIN("_glesLightSource[1].diffuse", kShaderVecLight1Diffuse),
+ BIND_VEC_BUILTIN("_glesLightSource[2].diffuse", kShaderVecLight2Diffuse),
+ BIND_VEC_BUILTIN("_glesLightSource[3].diffuse", kShaderVecLight3Diffuse),
+ BIND_VEC_BUILTIN("_glesLightSource[0].position", kShaderVecLight0Position),
+ BIND_VEC_BUILTIN("_glesLightSource[1].position", kShaderVecLight1Position),
+ BIND_VEC_BUILTIN("_glesLightSource[2].position", kShaderVecLight2Position),
+ BIND_VEC_BUILTIN("_glesLightSource[3].position", kShaderVecLight3Position),
+ BIND_VEC_BUILTIN("_glesLightSource[0].spotDirection", kShaderVecLight0SpotDirection),
+ BIND_VEC_BUILTIN("_glesLightSource[1].spotDirection", kShaderVecLight1SpotDirection),
+ BIND_VEC_BUILTIN("_glesLightSource[2].spotDirection", kShaderVecLight2SpotDirection),
+ BIND_VEC_BUILTIN("_glesLightSource[3].spotDirection", kShaderVecLight3SpotDirection),
+ BIND_VEC_BUILTIN("_glesLightSource[0].atten", kShaderVecLight0Atten),
+ BIND_VEC_BUILTIN("_glesLightSource[1].atten", kShaderVecLight1Atten),
+ BIND_VEC_BUILTIN("_glesLightSource[2].atten", kShaderVecLight2Atten),
+ BIND_VEC_BUILTIN("_glesLightSource[3].atten", kShaderVecLight3Atten),
+ BIND_VEC_BUILTIN("_glesLightModel.ambient", kShaderVecLightModelAmbient),
+};
+
+
+const char* GetGLSLES3PropertyNameRemap (const char* name)
+{
+ for (int i = 0; i < ARRAY_SIZE(kglslesProperties); i++)
+ {
+ const GLSLESProperty& prop = kglslesProperties[i];
+ if (strcmp(name, prop.glName) == 0)
+ return prop.unityName;
+ }
+ return NULL;
+}
+
+#endif // GFX_SUPPORTS_OPENGLES30
diff --git a/Runtime/GfxDevice/opengles30/GpuPropertiesGLES30.h b/Runtime/GfxDevice/opengles30/GpuPropertiesGLES30.h
new file mode 100644
index 0000000..86b980e
--- /dev/null
+++ b/Runtime/GfxDevice/opengles30/GpuPropertiesGLES30.h
@@ -0,0 +1,3 @@
+#pragma once
+
+const char* GetGLSLES3PropertyNameRemap (const char* name);
diff --git a/Runtime/GfxDevice/opengles30/IncludesGLES30.h b/Runtime/GfxDevice/opengles30/IncludesGLES30.h
new file mode 100644
index 0000000..260bea5
--- /dev/null
+++ b/Runtime/GfxDevice/opengles30/IncludesGLES30.h
@@ -0,0 +1 @@
+#include "Runtime/GfxDevice/opengles/IncludesGLES.h" \ No newline at end of file
diff --git a/Runtime/GfxDevice/opengles30/RenderTextureGLES30.cpp b/Runtime/GfxDevice/opengles30/RenderTextureGLES30.cpp
new file mode 100644
index 0000000..7f5a92f
--- /dev/null
+++ b/Runtime/GfxDevice/opengles30/RenderTextureGLES30.cpp
@@ -0,0 +1,551 @@
+#include "UnityPrefix.h"
+#include "RenderTextureGLES30.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+#include "Runtime/Shaders/GraphicsCaps.h"
+#include "Runtime/Graphics/Image.h"
+#include "Runtime/Utilities/ArrayUtility.h"
+#include "Runtime/Utilities/BitUtility.h"
+#include "Runtime/Graphics/RenderTexture.h"
+#include "Runtime/Graphics/ScreenManager.h"
+#include "IncludesGLES30.h"
+#include "AssertGLES30.h"
+#include "DebugGLES30.h"
+#include "TextureIdMapGLES30.h"
+#include "UtilsGLES30.h"
+
+#if UNITY_ANDROID
+ #include "PlatformDependent/AndroidPlayer/EntryPoint.h"
+#endif
+
+#if 1
+ #define DBG_LOG_RT_GLES30(...) {}
+#else
+ #define DBG_LOG_RT_GLES30(...) {printf_console(__VA_ARGS__);printf_console("\n");}
+#endif
+
+#if GFX_SUPPORTS_OPENGLES30
+
+namespace
+{
+
+static const UInt32 kCubeFacesES3[] =
+{
+ GL_TEXTURE_CUBE_MAP_POSITIVE_X,
+ GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
+ GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
+ GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
+ GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
+ GL_TEXTURE_CUBE_MAP_NEGATIVE_Z,
+};
+
+inline bool IsDepthStencilFormat (UInt32 format)
+{
+ switch (format)
+ {
+ case GL_DEPTH24_STENCIL8:
+ case GL_DEPTH32F_STENCIL8:
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+const char* GetFBOStatusName (UInt32 status)
+{
+ switch (status)
+ {
+ case GL_FRAMEBUFFER_COMPLETE: return "COMPLETE";
+ case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: return "INCOMPLETE_ATTACHMENT";
+ case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: return "INCOMPLETE_MISSING_ATTACHMENT";
+ case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS: return "INCOMPLETE_DIMENSIONS";
+ case GL_FRAMEBUFFER_UNSUPPORTED: return "UNSUPPORTED";
+ default: return "unknown error";
+ }
+}
+
+} // anonymous
+
+// RenderSurfaceGLES30
+RenderSurfaceGLES30::RenderSurfaceGLES30 (Type type, UInt32 format, int w, int h, int numSamples)
+ : m_type (type)
+ , m_format (format)
+ , m_flags (0)
+{
+ RenderSurfaceBase_Init(*this);
+ width = w;
+ height = h;
+ samples = numSamples;
+}
+
+// RenderTexture2DGLES30
+
+RenderTexture2DGLES30::RenderTexture2DGLES30 (TextureID texID, UInt32 format, int w, int h)
+ : RenderSurfaceGLES30 (kTypeTexture2D, format, w, h, 1)
+{
+ textureID = texID;
+
+ TransferFormatGLES30 transferFmt = GetTransferFormatGLES30(format);
+ GLuint glTexID = TextureIdMapGLES30_QueryOrCreate(textureID);
+
+ Assert(glTexID != 0);
+
+ GetRealGfxDevice().SetTexture(kShaderFragment, 0, 0, textureID, kTexDim2D, std::numeric_limits<float>::infinity());
+ GLES_CHK(glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, transferFmt.format, transferFmt.dataType, 0));
+}
+
+RenderTexture2DGLES30::~RenderTexture2DGLES30 (void)
+{
+ // \todo [2013-04-29 pyry] Set texture storage to null?
+}
+
+void RenderTexture2DGLES30::AttachColor (int ndx, CubemapFace face)
+{
+ Assert(face == kCubeFaceUnknown);
+ GLES_CHK(glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0+ndx, GL_TEXTURE_2D, GetGLTextureID(), 0));
+}
+
+void RenderTexture2DGLES30::AttachDepthStencil (CubemapFace face)
+{
+ // \note Using GL_DEPTH_STENCIL_ATTACHMENT would eliminate one more call, but unfortunately
+ // that is broken at least on ARM GLES3 EMU.
+ Assert(face == kCubeFaceUnknown);
+
+ GLES_CHK(glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, GetGLTextureID(), 0));
+
+ if (IsDepthStencilFormat(m_format))
+ GLES_CHK(glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, GetGLTextureID(), 0));
+}
+
+// RenderTextureCubeGLES30
+
+RenderTextureCubeGLES30::RenderTextureCubeGLES30 (TextureID texID, UInt32 format, int w, int h)
+ : RenderSurfaceGLES30 (kTypeTextureCube, format, w, h, 1)
+{
+ textureID = texID;
+
+ TransferFormatGLES30 transferFmt = GetTransferFormatGLES30(format);
+ GLuint glTexID = TextureIdMapGLES30_QueryOrCreate(textureID);
+
+ Assert(glTexID != 0);
+
+ GetRealGfxDevice().SetTexture(kShaderFragment, 0, 0, textureID, kTexDimCUBE, std::numeric_limits<float>::infinity());
+
+ for (int ndx = 0; ndx < 6; ndx++)
+ GLES_CHK(glTexImage2D(kCubeFacesES3[ndx], 0, format, width, height, 0, transferFmt.format, transferFmt.dataType, 0));
+}
+
+RenderTextureCubeGLES30::~RenderTextureCubeGLES30 (void)
+{
+ // \todo [2013-04-29 pyry] Set texture storage to null?
+}
+
+void RenderTextureCubeGLES30::AttachColor (int ndx, CubemapFace face)
+{
+ const int faceIndex = clamp<int>(face,0,5); // can be passed -1 when restoring from previous RT
+ GLES_CHK(glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0+ndx, kCubeFacesES3[faceIndex], GetGLTextureID(), 0));
+}
+
+void RenderTextureCubeGLES30::AttachDepthStencil (CubemapFace face)
+{
+ const int faceIndex = clamp<int>(face,0,5); // can be passed -1 when restoring from previous RT
+
+ GLES_CHK(glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, kCubeFacesES3[faceIndex], GetGLTextureID(), 0));
+
+ if (IsDepthStencilFormat(m_format))
+ GLES_CHK(glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, kCubeFacesES3[faceIndex], GetGLTextureID(), 0));
+}
+
+// RenderBufferGLES30
+
+RenderBufferGLES30::RenderBufferGLES30 (UInt32 format, int w, int h, int numSamples)
+ : RenderSurfaceGLES30 (kTypeRenderBuffer, format, w, h, numSamples)
+ , m_bufferID (0)
+{
+ glGenRenderbuffers(1, (GLuint*)&m_bufferID);
+ glBindRenderbuffer(GL_RENDERBUFFER, m_bufferID);
+
+ if (numSamples > 1)
+ GLES_CHK(glRenderbufferStorageMultisample(GL_RENDERBUFFER, numSamples, format, width, height));
+ else
+ GLES_CHK(glRenderbufferStorage(GL_RENDERBUFFER, format, width, height));
+}
+
+RenderBufferGLES30::~RenderBufferGLES30 (void)
+{
+ if (m_bufferID)
+ glDeleteRenderbuffers(1, (GLuint*)&m_bufferID);
+}
+
+void RenderBufferGLES30::AttachColor (int ndx, CubemapFace face)
+{
+ Assert(face == kCubeFaceUnknown);
+ GLES_CHK(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0+ndx, GL_RENDERBUFFER, m_bufferID));
+}
+
+void RenderBufferGLES30::AttachDepthStencil (CubemapFace face)
+{
+ Assert(face == kCubeFaceUnknown);
+
+ GLES_CHK(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, m_bufferID));
+
+ if (IsDepthStencilFormat(m_format))
+ GLES_CHK(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, m_bufferID));
+}
+
+void RenderBufferGLES30::Disown (void)
+{
+ m_bufferID = 0;
+}
+
+// RenderBufferCubeGLES30
+
+RenderBufferCubeGLES30::RenderBufferCubeGLES30 (UInt32 format, int w, int h, int numSamples)
+ : RenderSurfaceGLES30 (kTypeRenderBufferCube, format, w, h, numSamples)
+{
+ memset(&m_buffers[0], 0, sizeof(m_buffers));
+
+ for (int i = 0; i < 6; i++)
+ m_buffers[i] = new RenderBufferGLES30(format, width, height, numSamples);
+}
+
+RenderBufferCubeGLES30::~RenderBufferCubeGLES30 (void)
+{
+ for (int i = 0; i < 6; i++)
+ delete m_buffers[i];
+}
+
+void RenderBufferCubeGLES30::AttachColor (int ndx, CubemapFace face)
+{
+ const int faceIndex = clamp<int>(face,0,5); // can be passed -1 when restoring from previous RT
+ m_buffers[faceIndex]->AttachColor(ndx, kCubeFaceUnknown);
+}
+
+void RenderBufferCubeGLES30::AttachDepthStencil (CubemapFace face)
+{
+ const int faceIndex = clamp<int>(face,0,5); // can be passed -1 when restoring from previous RT
+ m_buffers[faceIndex]->AttachDepthStencil(kCubeFaceUnknown);
+}
+
+// FramebufferAttachmentsGLES30
+
+FramebufferAttachmentsGLES30::FramebufferAttachmentsGLES30 (int numColorAttachments, RenderSurfaceGLES30* colorAttachments, RenderSurfaceGLES30* depthStencilAttachment, CubemapFace face)
+ : numColorAttachments (numColorAttachments)
+ , depthStencil (depthStencilAttachment)
+ , cubemapFace (face)
+{
+ for (int ndx = 0; ndx < numColorAttachments; ndx++)
+ color[ndx] = &colorAttachments[ndx];
+
+ for (int ndx = numColorAttachments; ndx < kMaxColorAttachments; ndx++)
+ color[ndx] = 0;
+}
+
+FramebufferAttachmentsGLES30::FramebufferAttachmentsGLES30 (void)
+ : numColorAttachments (0)
+ , depthStencil (0)
+ , cubemapFace (kCubeFaceUnknown)
+{
+ memset(&color[0], 0, sizeof(color));
+}
+
+// FramebufferObjectGLES30
+
+FramebufferObjectGLES30::FramebufferObjectGLES30 (const FramebufferAttachmentsGLES30& attachments)
+ : m_fboID (0)
+ , m_attachments (attachments)
+{
+ GLuint oldFbo = 0;
+ glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*)&oldFbo);
+
+ glGenFramebuffers(1, (GLuint*)&m_fboID);
+ GLES_CHK(glBindFramebuffer(GL_FRAMEBUFFER, m_fboID));
+
+ for (int colorNdx = 0; colorNdx < attachments.numColorAttachments; colorNdx++)
+ {
+ if (m_attachments.color[colorNdx] && m_attachments.color[colorNdx]->GetType() != RenderSurfaceGLES30::kTypeDummy)
+ m_attachments.color[colorNdx]->AttachColor(colorNdx, m_attachments.cubemapFace);
+ }
+
+ if (m_attachments.depthStencil)
+ m_attachments.depthStencil->AttachDepthStencil(m_attachments.cubemapFace);
+
+ UInt32 status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
+ if (status != GL_FRAMEBUFFER_COMPLETE)
+ {
+ const char* statusName = GetFBOStatusName(status);
+ ErrorStringMsg("Framebuffer is not complete: %s", statusName);
+ Assert(status == GL_FRAMEBUFFER_COMPLETE);
+ }
+
+ // \todo [2013-04-30 pyry] We should really fail object construction if fbo is not complete.
+ GLES_CHK(glBindFramebuffer(GL_FRAMEBUFFER, oldFbo));
+}
+
+FramebufferObjectGLES30::~FramebufferObjectGLES30 (void)
+{
+ if (m_fboID)
+ glDeleteFramebuffers(1, (GLuint*)&m_fboID);
+}
+
+void FramebufferObjectGLES30::Disown (void)
+{
+ m_fboID = 0;
+}
+
+RenderSurfaceGLES30* FramebufferObjectGLES30::GetColorAttachment (int ndx)
+{
+ Assert(0 <= ndx && ndx <= FramebufferAttachmentsGLES30::kMaxColorAttachments);
+ return m_attachments.color[ndx];
+}
+
+const RenderSurfaceGLES30* FramebufferObjectGLES30::GetColorAttachment (int ndx) const
+{
+ Assert(0 <= ndx && ndx <= FramebufferAttachmentsGLES30::kMaxColorAttachments);
+ return m_attachments.color[ndx];
+}
+
+RenderSurfaceGLES30* FramebufferObjectGLES30::GetDepthStencilAttachment (void)
+{
+ return m_attachments.depthStencil;
+}
+
+const RenderSurfaceGLES30* FramebufferObjectGLES30::GetDepthStencilAttachment (void) const
+{
+ return m_attachments.depthStencil;
+}
+
+// FramebufferObjectManagerGLES30
+
+bool CompareFramebufferAttachmentsGLES30::operator() (const FramebufferAttachmentsGLES30* a, const FramebufferAttachmentsGLES30* b) const
+{
+ if (a->numColorAttachments < b->numColorAttachments) return true;
+ else if (a->numColorAttachments > b->numColorAttachments) return false;
+
+ if (a->depthStencil < b->depthStencil) return true;
+ else if (a->depthStencil > b->depthStencil) return false;
+
+ if (a->cubemapFace < b->cubemapFace) return true;
+ else if (a->cubemapFace > b->cubemapFace) return false;
+
+ for (int ndx = 0; ndx < a->numColorAttachments; ndx++)
+ {
+ if (a->color[ndx] < b->color[ndx]) return true;
+ else if (a->color[ndx] > b->color[ndx]) return false;
+ }
+
+ return false; // Equal.
+}
+
+bool CompareRenderBufferParamsGLES30::operator() (const RenderBufferParamsGLES30& a, const RenderBufferParamsGLES30& b) const
+{
+ if (a.format < b.format) return true;
+ else if (a.format > b.format) return false;
+
+ if (a.width < b.width) return true;
+ else if (a.width > b.width) return false;
+
+ if (a.height < b.height) return true;
+ else if (a.height > b.height) return false;
+
+ return false; // Equal.
+}
+
+FramebufferObjectManagerGLES30::FramebufferObjectManagerGLES30 (void)
+{
+}
+
+FramebufferObjectManagerGLES30::~FramebufferObjectManagerGLES30 (void)
+{
+ Clear();
+}
+
+void FramebufferObjectManagerGLES30::InvalidateObjects (void)
+{
+ // Call disown on all FBOs first.
+ for (FramebufferObjectMapGLES30::iterator iter = m_fboMap.begin(); iter != m_fboMap.end(); ++iter)
+ iter->second->Disown();
+
+ for (RenderBufferMapGLES30::iterator iter = m_rbufMap.begin(); iter != m_rbufMap.end(); ++iter)
+ iter->second->Disown();
+
+ // Clear objects.
+ Clear();
+}
+
+void FramebufferObjectManagerGLES30::Clear (void)
+{
+ // \todo [pyry] This actually invalidates keys as well. Is that okay in clear()?
+ for (FramebufferObjectMapGLES30::iterator iter = m_fboMap.begin(); iter != m_fboMap.end(); ++iter)
+ delete iter->second;
+ m_fboMap.clear();
+
+ for (RenderBufferMapGLES30::iterator iter = m_rbufMap.begin(); iter != m_rbufMap.end(); ++iter)
+ delete iter->second;
+ m_rbufMap.clear();
+}
+
+void FramebufferObjectManagerGLES30::InvalidateSurface (const RenderSurfaceGLES30* surface)
+{
+ std::vector<FramebufferObjectGLES30*> deleteFbos;
+
+ for (FramebufferObjectMapGLES30::iterator iter = m_fboMap.begin(); iter != m_fboMap.end(); ++iter)
+ {
+ if (IsInFramebufferAttachmentsGLES30(*iter->first, surface))
+ deleteFbos.push_back(iter->second);
+ }
+
+ for (std::vector<FramebufferObjectGLES30*>::iterator iter = deleteFbos.begin(); iter != deleteFbos.end(); ++iter)
+ {
+ m_fboMap.erase((*iter)->GetAttachments());
+ delete *iter;
+ }
+}
+
+FramebufferObjectGLES30* FramebufferObjectManagerGLES30::GetFramebufferObject (const FramebufferAttachmentsGLES30& attachments)
+{
+ // Try to fetch from cache.
+ {
+ FramebufferObjectMapGLES30::const_iterator fboPos = m_fboMap.find(&attachments);
+ if (fboPos != m_fboMap.end())
+ return fboPos->second;
+ }
+
+ DBG_LOG_RT_GLES30("FramebufferObjectManagerGLES30::GetFramebufferObject(): cache miss, creating FBO");
+
+ // Not found - create a new one and insert.
+ {
+ FramebufferObjectGLES30* fbo = new FramebufferObjectGLES30(attachments);
+ m_fboMap.insert(std::make_pair(fbo->GetAttachments(), fbo));
+ return fbo;
+ }
+}
+
+RenderBufferGLES30* FramebufferObjectManagerGLES30::GetRenderBuffer (UInt32 format, int width, int height)
+{
+ RenderBufferParamsGLES30 params(format, width, height);
+
+ {
+ RenderBufferMapGLES30::const_iterator pos = m_rbufMap.find(params);
+ if (pos != m_rbufMap.end())
+ return pos->second;
+ }
+
+ DBG_LOG_RT_GLES30("FramebufferObjectManagerGLES30::GetRenderBuffer(): cache miss, creating RBO");
+
+ {
+ RenderBufferGLES30* buf = new RenderBufferGLES30(format, width, height, 1);
+ m_rbufMap.insert(std::make_pair(params, buf));
+ return buf;
+ }
+}
+
+// Utilities
+
+bool IsInFramebufferAttachmentsGLES30 (const FramebufferAttachmentsGLES30& attachments, const RenderSurfaceGLES30* renderSurface)
+{
+ for (int ndx = 0; ndx < attachments.numColorAttachments; ndx++)
+ {
+ if (attachments.color[ndx] == renderSurface)
+ return true;
+ }
+
+ if (attachments.depthStencil == renderSurface)
+ return true;
+
+ return false;
+}
+
+void BindFramebufferObjectGLES30 (FramebufferObjectGLES30* fbo)
+{
+ Assert(fbo != 0);
+
+ GLenum drawBuffers [FramebufferAttachmentsGLES30::kMaxColorAttachments];
+ GLenum discardBuffers [FramebufferAttachmentsGLES30::kMaxColorAttachments+1];
+ int drawBufferNdx = 0;
+ int discardBufferNdx = 0;
+
+ for (int ndx = 0; ndx < fbo->GetNumColorAttachments(); ndx++)
+ {
+ RenderSurfaceGLES30* attachment = fbo->GetColorAttachment(ndx);
+
+ if (attachment)
+ drawBuffers[drawBufferNdx++] = GL_COLOR_ATTACHMENT0+ndx;
+
+ if (attachment && (attachment->GetFlags() & RenderSurfaceGLES30::kDiscardOnBind))
+ {
+ if(fbo->GetFboID() == 0)
+ discardBuffers[discardBufferNdx++] = GL_COLOR;
+ else
+ discardBuffers[discardBufferNdx++] = GL_COLOR_ATTACHMENT0+ndx;
+ attachment->SetFlags(attachment->GetFlags() & ~RenderSurfaceGLES30::kDiscardOnBind);
+ }
+ }
+
+ if (fbo->GetDepthStencilAttachment())
+ {
+ RenderSurfaceGLES30* depthStencilAttachment = fbo->GetDepthStencilAttachment();
+ if (depthStencilAttachment->GetFlags() & RenderSurfaceGLES30::kDiscardOnBind && depthStencilAttachment->GetType() != RenderSurfaceGLES30::kTypeDummy)
+ {
+ if(fbo->GetFboID() == 0)
+ {
+ discardBuffers[discardBufferNdx++] = GL_DEPTH;
+ discardBuffers[discardBufferNdx++] = GL_STENCIL;
+ }
+ else
+ {
+ discardBuffers[discardBufferNdx++] = GL_DEPTH_ATTACHMENT;
+ discardBuffers[discardBufferNdx++] = GL_STENCIL_ATTACHMENT;
+ }
+
+ depthStencilAttachment->SetFlags(depthStencilAttachment->GetFlags() & ~RenderSurfaceGLES30::kDiscardOnBind);
+ }
+ }
+
+ GLES_CHK(glBindFramebuffer(GL_FRAMEBUFFER, fbo->GetFboID()));
+ GLES_CHK(glDrawBuffers(drawBufferNdx, &drawBuffers[0]));
+
+ if (discardBufferNdx > 0)
+ GLES_CHK(glInvalidateFramebuffer(GL_FRAMEBUFFER, discardBufferNdx, &discardBuffers[0]));
+}
+
+void BindDefaultFramebufferGLES30 (void)
+{
+ GLuint defaultDrawBuffer = GL_BACK;
+ GLES_CHK(glBindFramebuffer(GL_FRAMEBUFFER, 0));
+ GLES_CHK(glDrawBuffers(1, &defaultDrawBuffer));
+}
+
+FramebufferObjectGLES30* GetResolveFramebufferObjectGLES30 (FramebufferObjectManagerGLES30* fboManager, UInt32 colorFormat, UInt32 depthStencilFormat, int width, int height)
+{
+ RenderBufferGLES30* colorBuf = colorFormat != 0 ? fboManager->GetRenderBuffer(colorFormat, width, height) : 0;
+ RenderBufferGLES30* depthBuf = depthStencilFormat != 0 ? fboManager->GetRenderBuffer(depthStencilFormat, width, height) : 0;
+ FramebufferAttachmentsGLES30 attachments;
+
+ attachments.color[0] = colorBuf;
+ attachments.depthStencil = depthBuf;
+ attachments.numColorAttachments = colorBuf ? 1 : 0;
+
+ return fboManager->GetFramebufferObject(attachments);
+}
+
+// back buffer
+RenderSurfaceBase* CreateBackBufferColorSurfaceGLES3()
+{
+ DummyRenderSurfaceGLES30* rs = new DummyRenderSurfaceGLES30(0,0);
+ RenderSurfaceBase_InitColor(*rs);
+ rs->backBuffer = true;
+
+ return rs;
+}
+RenderSurfaceBase* CreateBackBufferDepthSurfaceGLES3()
+{
+ DummyRenderSurfaceGLES30* rs = new DummyRenderSurfaceGLES30(0,0);
+ RenderSurfaceBase_InitDepth(*rs);
+ rs->backBuffer = true;
+
+ return rs;
+}
+
+
+#endif // GFX_SUPPORTS_OPENGLES30
diff --git a/Runtime/GfxDevice/opengles30/RenderTextureGLES30.h b/Runtime/GfxDevice/opengles30/RenderTextureGLES30.h
new file mode 100644
index 0000000..adc1898
--- /dev/null
+++ b/Runtime/GfxDevice/opengles30/RenderTextureGLES30.h
@@ -0,0 +1,267 @@
+#pragma once
+
+#if GFX_SUPPORTS_OPENGLES30
+
+#include "Configuration/UnityConfigure.h"
+#include "Runtime/GfxDevice/GfxDeviceConfigure.h"
+#include "Runtime/GfxDevice/GfxDeviceObjects.h"
+#include "Runtime/GfxDevice/GfxDeviceTypes.h"
+#include "Runtime/GfxDevice/TextureIdMap.h"
+#include "Runtime/Graphics/RenderSurface.h"
+
+#include <map>
+
+//! Render surface base class (implementations in RenderTextureGLES30 or RenderBufferGLES30)
+class RenderSurfaceGLES30 : public RenderSurfaceBase
+{
+public:
+ enum Type
+ {
+ kTypeTexture2D = 0,
+ kTypeTextureCube,
+ kTypeRenderBuffer,
+ kTypeRenderBufferCube, //!< RenderSurface that contains one RenderBuffer for each cubemap face. Used for MSAA cubemap rendering.
+ kTypeDummy, //!< Dummy surface (no real storage), used when doing depth only.
+ };
+
+ enum Flags
+ {
+ kDiscardOnBind = (1<<0), //!< Discard this attachment when bound as render target next time.
+ };
+
+ virtual ~RenderSurfaceGLES30 (void) {}
+
+ Type GetType (void) const { return m_type; }
+ UInt32 GetFormat (void) const { return m_format; }
+ int GetWidth (void) const { return width; }
+ int GetHeight (void) const { return height; }
+ int GetNumSamples (void) const { return samples; }
+
+ UInt32 GetFlags (void) const { return m_flags; }
+ void SetFlags (UInt32 flags) { m_flags = flags; }
+
+ virtual void AttachColor (int ndx, CubemapFace face) = 0;
+ virtual void AttachDepthStencil (CubemapFace face) = 0;
+
+protected:
+ RenderSurfaceGLES30 (Type type, UInt32 format, int width, int height, int numSamples);
+
+ const Type m_type; //!< RenderSurface type
+ const UInt32 m_format; //!< Internal format
+
+ UInt32 m_flags; //!< Flags.
+
+private:
+ RenderSurfaceGLES30 (const RenderSurfaceGLES30& other); // Not allowed!
+ RenderSurfaceGLES30& operator= (const RenderSurfaceGLES30& other); // Not allowed!
+};
+
+class DummyRenderSurfaceGLES30 : public RenderSurfaceGLES30
+{
+public:
+ DummyRenderSurfaceGLES30 (int width, int height) : RenderSurfaceGLES30(kTypeDummy, 0, width, height, 1) {}
+ ~DummyRenderSurfaceGLES30 (void) {}
+
+ virtual void AttachColor (int ndx, CubemapFace face) {}
+ virtual void AttachDepthStencil (CubemapFace face) {}
+};
+
+class RenderTexture2DGLES30 : public RenderSurfaceGLES30
+{
+public:
+ RenderTexture2DGLES30 (TextureID textureID, UInt32 format, int width, int height);
+ ~RenderTexture2DGLES30 (void);
+
+ virtual void AttachColor (int ndx, CubemapFace face);
+ virtual void AttachDepthStencil (CubemapFace face);
+
+ TextureID GetTextureID (void) const { return textureID; }
+ UInt32 GetGLTextureID (void) const { return (UInt32)TextureIdMap::QueryNativeTexture(textureID); }
+};
+
+class RenderTextureCubeGLES30 : public RenderSurfaceGLES30
+{
+public:
+ RenderTextureCubeGLES30 (TextureID textureID, UInt32 format, int width, int height);
+ ~RenderTextureCubeGLES30 (void);
+
+ virtual void AttachColor (int ndx, CubemapFace face);
+ virtual void AttachDepthStencil (CubemapFace face);
+
+ TextureID GetTextureID (void) const { return textureID; }
+ UInt32 GetGLTextureID (void) const { return (UInt32)TextureIdMap::QueryNativeTexture(textureID); }
+};
+
+class RenderBufferGLES30 : public RenderSurfaceGLES30
+{
+public:
+ RenderBufferGLES30 (UInt32 format, int width, int height, int numSamples);
+ ~RenderBufferGLES30 (void);
+
+ virtual void AttachColor (int ndx, CubemapFace face);
+ virtual void AttachDepthStencil (CubemapFace face);
+
+ UInt32 GetBufferID (void) const { return m_bufferID; }
+
+ //! RenderBuffer specific: remove current buffer handle and do not attempt to destroy it.
+ void Disown (void);
+
+private:
+ UInt32 m_bufferID;
+};
+
+class RenderBufferCubeGLES30 : public RenderSurfaceGLES30
+{
+public:
+ RenderBufferCubeGLES30 (UInt32 format, int width, int height, int numSamples);
+ ~RenderBufferCubeGLES30 (void);
+
+ virtual void AttachColor (int ndx, CubemapFace face);
+ virtual void AttachDepthStencil (CubemapFace face);
+
+ const RenderBufferGLES30& GetBuffer (CubemapFace face) const;
+
+private:
+ RenderBufferGLES30* m_buffers[6];
+};
+
+//!< FBO attachment container - used as FBO map key and in FramebufferObject
+class FramebufferAttachmentsGLES30
+{
+public:
+ enum
+ {
+ kMaxColorAttachments = 4
+ };
+
+ FramebufferAttachmentsGLES30 (int numColorAttachments, RenderSurfaceGLES30* colorAttachments, RenderSurfaceGLES30* depthStencilAttachment, CubemapFace face);
+ FramebufferAttachmentsGLES30 (void);
+
+ int numColorAttachments; //!< Number of valid attachments in color[] array. Rest are zero-filled.
+ RenderSurfaceGLES30* color[kMaxColorAttachments]; //!< Color attachments.
+ RenderSurfaceGLES30* depthStencil; //!< Depth or depth-stencil attachment, if such is used.
+ CubemapFace cubemapFace; //!< Applies to cubemap attachments only.
+};
+
+class FramebufferObjectGLES30
+{
+public:
+ FramebufferObjectGLES30 (const FramebufferAttachmentsGLES30& attachments);
+ ~FramebufferObjectGLES30 (void);
+
+ UInt32 GetFboID (void) const { return m_fboID; }
+
+ const FramebufferAttachmentsGLES30* GetAttachments (void) const { return &m_attachments; }
+
+ int GetNumColorAttachments (void) const { return m_attachments.numColorAttachments; }
+ CubemapFace GetCubemapFace (void) const { return m_attachments.cubemapFace; }
+
+ RenderSurfaceGLES30* GetColorAttachment (int ndx);
+ const RenderSurfaceGLES30* GetColorAttachment (int ndx) const;
+
+ RenderSurfaceGLES30* GetDepthStencilAttachment (void);
+ const RenderSurfaceGLES30* GetDepthStencilAttachment (void) const;
+
+ //! Disown and remove fbo handle. Used if destructor should not try to delete fbo for some reason (context lost for example).
+ void Disown (void);
+
+private:
+ UInt32 m_fboID;
+ FramebufferAttachmentsGLES30 m_attachments;
+};
+
+class RenderBufferParamsGLES30
+{
+public:
+ UInt32 format;
+ int width;
+ int height;
+ // \note No numSamples since these are currently used for allocating resolve buffers only.
+ // Sample count may be added if temporary buffers are required for something else.
+
+ RenderBufferParamsGLES30 (UInt32 format_, int width_, int height_)
+ : format (format_)
+ , width (width_)
+ , height (height_)
+ {
+ }
+};
+
+// FramebufferObject map implementation.
+//
+// Pointers to actual key data is used, since they are cheaper to move around. When inserting,
+// key pointer is acquired from FramebufferObject. When searching, it is up to user to provide
+// valid pointer (usually from stack).
+struct CompareFramebufferAttachmentsGLES30
+{
+ bool operator() (const FramebufferAttachmentsGLES30* a, const FramebufferAttachmentsGLES30* b) const;
+};
+typedef std::map<const FramebufferAttachmentsGLES30*, FramebufferObjectGLES30*, CompareFramebufferAttachmentsGLES30> FramebufferObjectMapGLES30;
+
+// RenderBuffer map implementation.
+struct CompareRenderBufferParamsGLES30
+{
+ bool operator() (const RenderBufferParamsGLES30& a, const RenderBufferParamsGLES30& b) const;
+};
+typedef std::map<RenderBufferParamsGLES30, RenderBufferGLES30*, CompareRenderBufferParamsGLES30> RenderBufferMapGLES30;
+
+class FramebufferObjectManagerGLES30
+{
+public:
+ FramebufferObjectManagerGLES30 (void);
+ ~FramebufferObjectManagerGLES30 (void);
+
+ //! Mark API objects invalid and clear cache (use on context loss)
+ void InvalidateObjects (void);
+
+ //!< Destroy all internal and API objects (clear cache and free objects).
+ void Clear (void);
+
+ //!< Destroy all FBOs where render surface is attached.
+ void InvalidateSurface (const RenderSurfaceGLES30* surface);
+
+ //! Create (or fetch from cache) framebuffer object to hold given attachment set.
+ FramebufferObjectGLES30* GetFramebufferObject (const FramebufferAttachmentsGLES30& attachments);
+
+ //! Create (or fetch from cache) temporary render buffer.
+ //
+ // Temporary render buffers are currently used as resolve buffers.
+ // Callee should not store returned buffer anywhere since it will be
+ // re-used.
+ RenderBufferGLES30* GetRenderBuffer (UInt32 format, int width, int height);
+
+private:
+ FramebufferObjectMapGLES30 m_fboMap;
+ RenderBufferMapGLES30 m_rbufMap;
+};
+
+//! Check if surface is in given FBO attachment list.
+bool IsInFramebufferAttachmentsGLES30 (const FramebufferAttachmentsGLES30& attachments, const RenderSurfaceGLES30* renderSurface);
+
+//! Bind FBO and setup for drawing
+//
+// This call setups FBO as rendering target:
+// 1) FBO is bound to current context
+// 2) If any of attachments have actions deferred to next bind defined, they are executed
+// + kDiscardOnBind: InvalidateFramebuffer() is called for those attachments
+// 3) DrawBuffers is set up based on attachments
+void BindFramebufferObjectGLES30 (FramebufferObjectGLES30* fbo);
+
+//! Bind default framebuffer (0)
+//
+// Changes FBO binding to 0 and sets GL_BACK as draw buffer.
+void BindDefaultFramebufferGLES30 (void);
+
+//! Get resolve FBO.
+//
+// \param fboManager FBO manager
+// \param colorFormat Color buffer format or 0 if not used.
+// \param depthStencilFormat Depth / depth-stencil format or 0 if not used
+// \param width
+// \param height
+//
+// Creates FBO for doing resolve. Buffers are allocated using fboManager->GetRenderBuffer()
+// and thus will be re-used by subsequent resolve FBOs if formats match.
+FramebufferObjectGLES30* GetResolveFramebufferObjectGLES30 (FramebufferObjectManagerGLES30* fboManager, UInt32 colorFormat, UInt32 depthStencilFormat, int width, int height);
+
+#endif // GFX_SUPPORTS_OPENGLES30
diff --git a/Runtime/GfxDevice/opengles30/ShaderGeneratorGLES30.cpp b/Runtime/GfxDevice/opengles30/ShaderGeneratorGLES30.cpp
new file mode 100644
index 0000000..afb9bdb
--- /dev/null
+++ b/Runtime/GfxDevice/opengles30/ShaderGeneratorGLES30.cpp
@@ -0,0 +1,824 @@
+#include "UnityPrefix.h"
+#include "FixedFunctionStateGLES30.h"
+#include "GpuProgramsGLES30.h"
+#include "ShaderGeneratorGLES30.h"
+#include "IncludesGLES30.h"
+#include "DebugGLES30.h"
+#include "External/shaderlab/Library/TextureBinding.h"
+#include "Runtime/Shaders/GraphicsCaps.h"
+#include "Runtime/GfxDevice/BuiltinShaderParams.h"
+#include "Runtime/GfxDevice/BuiltinShaderParamsNames.h"
+#include "Runtime/Utilities/BitUtility.h"
+
+#include <sstream>
+#include <assert.h>
+
+
+#define CMP_STATE(member) { \
+ if (a.member < b.member) \
+ return true; \
+ else if (b.member < a.member) \
+ return false; \
+}
+
+// builtins support only 4 lights
+const int kMaxEmulatedVertexLights = 4;//kMaxSupportedVertexLights;
+
+bool FullStateCompareGLES30::operator() (FixedFunctionStateGLES30 const& a, const FixedFunctionStateGLES30& b) const
+{
+ CMP_STATE(lightingEnabled)
+ CMP_STATE(specularEnabled)
+ CMP_STATE(lightCount)
+ CMP_STATE(onlyDirectionalLights)
+ CMP_STATE(lightType)
+ CMP_STATE(texUnitMatrix)
+ CMP_STATE(useUniformInsteadOfVertexColor)
+ CMP_STATE(useVertexColorAsAmbientAndDiffuse)
+ CMP_STATE(useVertexColorAsEmission)
+
+ CMP_STATE(fogMode)
+
+ CMP_STATE(texUnitCount);
+ for (int i = 0; i < a.texUnitCount; i++)
+ {
+ CMP_STATE(texUnitCube[i])
+ CMP_STATE(texUnitGen[i])
+ CMP_STATE(texUnitColorCombiner[i])
+ CMP_STATE(texUnitAlphaCombiner[i])
+ }
+
+ CMP_STATE(addSpecularAfterTexturing)
+ CMP_STATE(alphaTest)
+
+ return false; /* All equal, not lesser. */
+}
+
+bool VertexStateCompareGLES30::operator() (FixedFunctionStateGLES30 const& a, FixedFunctionStateGLES30 const& b) const
+{
+ CMP_STATE(lightingEnabled)
+ CMP_STATE(specularEnabled)
+ CMP_STATE(lightCount)
+ CMP_STATE(onlyDirectionalLights)
+ CMP_STATE(lightType)
+ CMP_STATE(texUnitMatrix)
+ CMP_STATE(useUniformInsteadOfVertexColor)
+ CMP_STATE(useVertexColorAsAmbientAndDiffuse)
+ CMP_STATE(useVertexColorAsEmission)
+
+ CMP_STATE(fogMode)
+
+ CMP_STATE(texUnitCount);
+ for (int i = 0; i < a.texUnitCount; i++)
+ {
+ CMP_STATE(texUnitCube[i])
+ CMP_STATE(texUnitGen[i])
+ }
+ return false; /* All equal, not lesser. */
+}
+
+bool FragmentStateCompareGLES30::operator() (FixedFunctionStateGLES30 const& a, FixedFunctionStateGLES30 const& b) const
+{
+ CMP_STATE(fogMode)
+ CMP_STATE(texUnitCount)
+
+ for (int i = 0; i < a.texUnitCount; i++)
+ {
+ CMP_STATE(texUnitCube[i])
+ CMP_STATE(texUnitGen[i])
+ CMP_STATE(texUnitColorCombiner[i])
+ CMP_STATE(texUnitAlphaCombiner[i])
+ }
+
+ CMP_STATE(addSpecularAfterTexturing)
+ CMP_STATE(alphaTest)
+
+ return false; /* All equal, not lesser. */
+}
+
+
+// --- VERTEX program ----------------------------------------------------------------------------
+
+std::string BuildVertexShaderSourceGLES30 (const FixedFunctionStateGLES30& state)
+{
+ DBG_SHADER_VERBOSE_GLES30("ShaderGeneratorGLES30::BuildVertexShaderSource()\n");
+ DBG_SHADER_VERBOSE_GLES30(" state: %s\n", state.ToString().c_str());
+
+ bool eyePositionRequired = (state.lightingEnabled && state.lightCount > 0 && !state.onlyDirectionalLights);
+ for (int i = 0; i < state.texUnitCount; i++)
+ if (state.texUnitGen[i]==kTexGenCubeReflect)
+ eyePositionRequired = true;
+
+ std::ostringstream src;
+
+ src << "#version 300 es\n";
+
+ /* Standard uniforms. */
+ src << "uniform " << FixedFunctionProgramGLES30::kSLPropTransformBlock.GetName() << " {\n";
+ src << " highp mat4 " << GetShaderInstanceMatrixParamName (kShaderInstanceMatMVP) << ";\n";
+ if (eyePositionRequired)
+ {
+ src << " highp mat4 " << GetShaderInstanceMatrixParamName (kShaderInstanceMatMV) << ";\n";
+ }
+ src << "};\n";
+
+ /* Default attributes. */
+ src << "in highp vec4 _glesVertex;\n";
+ src << "in mediump vec3 _glesNormal;\n";
+ if (state.useUniformInsteadOfVertexColor)
+ src << "uniform lowp vec4 _glesFFColor;\n";
+ else
+ src << "in lowp vec4 _glesColor;\n";
+
+ /* Default varyings. */
+ src << "out lowp vec4 v_color;\n";
+
+ if (state.fogMode > kFogDisabled)
+ {
+ src << "uniform highp vec4 _glesFogParams;\n";
+ src << "uniform lowp vec4 _glesFogColor;\n";
+ src << "out lowp vec4 _glesFogColorPreMul;\n";
+ src << "out lowp vec4 _glesFogVar;\n";
+ }
+
+ /* Texture coordinates and transformation matrices. */
+ bool needTexUnitMatrix = false;
+ for (int i = 0; i < state.texUnitCount; i++)
+ {
+ src << "in " << (state.NeedTexUnitMatrix(i)?"highp":"mediump") << " vec4 _glesMultiTexCoord" << i << ";\n";
+ if (!state.texUnitCube[i])
+ {
+ if (state.texUnitGen[i] == kTexGenObject)
+ src << "out highp vec4 v_texGenObjCoord" << i << ";\n";
+ else
+ src << "out mediump vec2 v_texCoord" << i << ";\n";
+ }
+ else
+ {
+ src << "out highp vec3 v_texCoord" << i << ";\n";
+ }
+
+ if (!needTexUnitMatrix && state.NeedTexUnitMatrix(i))
+ {
+ needTexUnitMatrix = true;
+ }
+ }
+
+ if (needTexUnitMatrix)
+ {
+ src << "uniform " << FixedFunctionProgramGLES30::kSLPropUVTransformBlock.GetName() << " {\n";
+ for (int i = 0; i < state.texUnitCount; ++i)
+ {
+ if(state.NeedTexUnitMatrix(i))
+ src << " highp mat4 " << GetShaderInstanceMatrixParamName (kShaderInstanceMatTexture0 + i) << ";\n";
+ }
+ src << "};\n";
+ }
+
+ /* Handle color -> material mapping. */
+ const char* ambientColor = state.useVertexColorAsAmbientAndDiffuse ? "vertexColor" : "_glesFrontMaterial.ambient";
+ const char* diffuseColor = state.useVertexColorAsAmbientAndDiffuse ? "vertexColor" : "_glesFrontMaterial.diffuse";
+ const char* emissionColor = state.useVertexColorAsEmission ? "vertexColor" : "_glesFrontMaterial.emission";
+
+ /* Light params. */
+ if (state.lightingEnabled)
+ {
+ src << "struct LightModelParameters {\n";
+ src << " vec4 ambient;\n";
+ src << "};\n";
+
+ src << "struct MaterialParameters {\n";
+ src << " vec4 emission;\n";
+ src << " vec4 ambient;\n";
+ src << " vec4 diffuse;\n";
+ src << " vec4 specular;\n";
+ src << " float shininess;\n";
+ src << "};\n";
+
+ src << "uniform LightModelParameters _glesLightModel;\n";
+ src << "uniform MaterialParameters _glesFrontMaterial;\n";
+
+ if (state.lightCount > 0)
+ {
+ src << "struct LightSourceParameters {\n";
+ src << " vec4 diffuse;\n";
+ src << " vec4 position;\n";
+ src << " vec3 spotDirection;\n";
+ src << " vec4 atten;\n";
+ src << "};\n";
+
+ src << "uniform vec4 " << GetBuiltinVectorParamName(kShaderVecLightModelAmbient) << ";\n";
+ for (int q = 0; q < kMaxEmulatedVertexLights; ++q)
+ {
+ src << "uniform vec4 " << GetBuiltinVectorParamName(kShaderVecLight0Diffuse + q) << ";\n";
+ src << "uniform vec4 " << GetBuiltinVectorParamName(kShaderVecLight0Position + q) << ";\n";
+ src << "uniform vec4 " << GetBuiltinVectorParamName(kShaderVecLight0SpotDirection + q) << ";\n";
+ src << "uniform vec4 " << GetBuiltinVectorParamName(kShaderVecLight0Atten + q) << ";\n";
+ }
+
+ src << "uniform mat3 _glesNormalMatrix;\n";
+
+ /* Compute functions. */
+ src << "\nvec3 direction (vec4 from, vec4 to)\n";
+ src << "{\n";
+ src << " return (to.xyz * from.w - from.xyz * to.w);\n";
+ src << "}\n";
+
+ src << "\nvec3 computeLighting(LightSourceParameters light, vec3 dirToLight, vec3 eyeNormal, vec4 vertexColor)\n";
+ src << "{\n";
+ src << " float NdotL = max(dot(eyeNormal, dirToLight), 0.0);\n";
+ // \note in Unity, light ambient is always zero
+ src << " vec3 color = NdotL * " << diffuseColor << ".rgb * light.diffuse.rgb;\n";
+ if (state.specularEnabled)
+ {
+ src << " if (NdotL > 0.0)\n";
+ src << " {\n";
+ src << " vec3 h = normalize(dirToLight + vec3(0.0, 0.0, 1.0));\n";
+ src << " float HdotN = max(dot(eyeNormal, h), 0.0);\n";
+ // \note in Unity, light specular color is always the same as diffuse color
+ src << " color += pow(HdotN, _glesFrontMaterial.shininess) * _glesFrontMaterial.specular.rgb * light.diffuse.rgb;\n";
+ src << " }\n";
+ }
+ src << " return color;\n";
+ src << "}\n";
+
+ src << "\nvec3 computeDirLight(LightSourceParameters light, vec3 eyeNormal, vec4 vertexColor)\n";
+ src << "{\n";
+ src << " vec3 dirToLight = light.position.xyz;\n";
+ // \note D3D backend uses min(val, 1.0). We use clamp(), because Img's wrapper is buggy!
+ src << " return clamp(computeLighting(light, dirToLight, eyeNormal, vertexColor), 0.0, 1.0);\n";
+ src << "}\n";
+
+ src << "\nvec3 computePointLight(LightSourceParameters light, vec4 eyePosition, vec3 eyeNormal, vec4 vertexColor)\n";
+ src << "{\n";
+ src << " vec3 dirToLight = direction(eyePosition, light.position);\n";
+ src << " float distSqr = dot(dirToLight, dirToLight);\n";
+ // \note in Unity, const attenuation=1.0, linear=0.0
+ src << " float att = 1.0 / (1.0 + light.atten.z * distSqr);\n";
+ src << " dirToLight *= inversesqrt(distSqr);\n";
+ // \note D3D backend uses min(val, 1.0). We use clamp(), because Img's wrapper is buggy!
+ src << " return clamp(att * computeLighting(light, dirToLight, eyeNormal, vertexColor), 0.0, 1.0);\n";
+ src << "}\n";
+
+ src << "\nvec3 computeSpotLight(LightSourceParameters light, vec4 eyePosition, vec3 eyeNormal, vec4 vertexColor)\n";
+ src << "{\n";
+ src << " vec3 dirToLight = direction(eyePosition, light.position);\n";
+ src << " float distSqr = dot(dirToLight, dirToLight);\n";
+ // \note in Unity, const atten=1.0, linear=0.0
+ src << " float att = 1.0 / (1.0 + light.atten.z * distSqr);\n";
+ src << " dirToLight *= inversesqrt(distSqr);\n";
+ src << " float rho = max(dot(dirToLight, light.spotDirection), 0.0);\n";
+ src << " float spotAtt = (rho - light.atten.x) * light.atten.y;\n";
+ src << " spotAtt = clamp(spotAtt, 0.0, 1.0);\n";
+ // \note D3D backend uses min(val, 1.0). We use clamp(), because Img's wrapper is buggy!
+ src << " return clamp(att * spotAtt * computeLighting(light, dirToLight, eyeNormal, vertexColor), 0.0, 1.0);\n";
+ //src << " return computeLighting(light, dirToLight, eyeNormal, vertexColor);\n"; // DEBUG DEBUG
+
+ src << "}\n";
+ }
+ }
+
+
+
+ /* Main body. */
+ src << "\nvoid main()\n";
+ src << "{\n";
+
+ /* Vertex transformation. */
+ src << " gl_Position = " << GetShaderInstanceMatrixParamName (kShaderInstanceMatMVP) << " * _glesVertex;\n";
+
+ /* Unpack vertex color if necessary. */
+ if (state.useUniformInsteadOfVertexColor)
+ src << " vec4 vertexColor = _glesFFColor;\n";
+ else
+ src << " vec4 vertexColor = _glesColor;\n";
+
+ if (eyePositionRequired)
+ src << " highp vec4 eyePosition = " << GetShaderInstanceMatrixParamName (kShaderInstanceMatMV) << " * _glesVertex;\n";
+
+ /* Pass and transform texture coordinates. */
+ for (int i = 0; i < state.texUnitCount; i++)
+ {
+ if (!state.texUnitCube[i])
+ {
+ if (state.texUnitGen[i] == kTexGenObject)
+ {
+ Assert(state.NeedTexUnitMatrix(i));
+ src << " v_texGenObjCoord" << i << " = ";
+ src << "(" << GetShaderInstanceMatrixParamName (kShaderInstanceMatTexture0 + i) << " * _glesMultiTexCoord" << i << ").xyzw;\n";
+ }
+ else
+ {
+ if(state.NeedTexUnitMatrix(i))
+ {
+ src << " vec4 tmpTexCoord" << i << " = (" << GetShaderInstanceMatrixParamName (kShaderInstanceMatTexture0 + i) << " * _glesMultiTexCoord" << i << ").xyzw;\n";
+ if(state.IsTexUnitProjected(i))
+ src << " v_texCoord" << i << " = tmpTexCoord" << i << ".xy / tmpTexCoord" << i << ".w;\n";
+ else
+ src << " v_texCoord" << i << " = tmpTexCoord" << i << ".xy;\n";
+ }
+ else
+ {
+ src << " v_texCoord" << i << " = _glesMultiTexCoord" << i << ".xy;\n";
+ }
+ }
+ }
+ else
+ {
+ src << " v_texCoord" << i << " = ";
+ src << "vec3(" << GetShaderInstanceMatrixParamName (kShaderInstanceMatTexture0 + i) << " * _glesMultiTexCoord" << i << ");\n";
+ if (state.texUnitGen[i] == kTexGenCubeReflect)
+ {
+ Assert(eyePositionRequired);
+ src << "{\n";
+ src << " vec3 n = v_texCoord"<< i <<".xyz;\n";
+ src << " v_texCoord"<< i <<" = reflect(eyePosition.xyz * eyePosition.w, n);\n";
+ src << "}\n";
+ }
+ }
+ }
+
+ switch (state.fogMode)
+ {
+ case kFogLinear:
+ src << " _glesFogVar = vec4(clamp (_glesFogParams.z * gl_Position.z + _glesFogParams.w, 0.0, 1.0)); _glesFogVar.a = 1.0;\n";
+ src << " _glesFogColorPreMul = _glesFogColor * (vec4(1.0)-_glesFogVar);\n";
+ break;
+ case kFogExp:
+ src << " float _patchFog = _glesFogParams.y * gl_Position.z;\n";
+ src << " _glesFogVar = vec4(clamp (exp2(-_patchFog), 0.0, 1.0)); _glesFogVar.a = 1.0;\n";
+ src << " _glesFogColorPreMul = _glesFogColor * (vec4(1.0)-_glesFogVar);\n";
+ break;
+ case kFogExp2:
+ src << " float _patchFog = _glesFogParams.x * gl_Position.z;\n";
+ src << " _patchFog = _patchFog * _patchFog;\n";
+ src << " _glesFogVar = vec4(clamp (exp2(-_patchFog), 0.0, 1.0)); _glesFogVar.a = 1.0;\n";
+ src << " _glesFogColorPreMul = _glesFogColor * (vec4(1.0)-_glesFogVar);\n";
+ break;
+ default:
+ break;
+ }
+
+ /* Vertex color computation. */
+ if (state.lightingEnabled)
+ {
+ src << " vec3 color = " << emissionColor << ".rgb + " << ambientColor << ".rgb * _glesLightModel.ambient.rgb;\n";
+// src << " color = vec3(0.0);\n"; // DEBUG DEBUG
+
+ if (state.lightCount > 0)
+ {
+ src << " vec3 eyeNormal = normalize(_glesNormalMatrix * _glesNormal);\n";
+ for (int i = 0; i < state.lightCount && i < kMaxEmulatedVertexLights; i++)
+ {
+ src << " LightSourceParameters light" << i << ";\n";
+ src << " light" << i << ".diffuse = " << GetBuiltinVectorParamName(kShaderVecLight0Diffuse + i) << ";\n";
+ src << " light" << i << ".position = " << GetBuiltinVectorParamName(kShaderVecLight0Position + i) << ";\n";
+ src << " light" << i << ".spotDirection = " << GetBuiltinVectorParamName(kShaderVecLight0SpotDirection + i) << ".xyz;\n";
+ src << " light" << i << ".atten = " << GetBuiltinVectorParamName(kShaderVecLight0Atten + i) << ";\n";
+
+ if(state.GetLightType(i) == kLightDirectional)
+ src << " color += computeDirLight(light" << i << ", eyeNormal, vertexColor);\n";
+ else if(state.GetLightType(i) == kLightSpot)
+ src << " color += computeSpotLight(light" << i << ", eyePosition, eyeNormal, vertexColor);\n";
+ else
+ src << " color += computePointLight(light" << i << ", eyePosition, eyeNormal, vertexColor);\n";
+
+ Assert(eyePositionRequired || state.GetLightType(i) == kLightDirectional);
+ }
+ }
+
+ src << " float alpha = " << diffuseColor << ".a;\n";
+ src << " v_color = vec4(color, alpha);\n";
+ }
+ else
+ {
+ src << " v_color = vertexColor;\n";
+ }
+
+ src << "}\n";
+
+ DBG_SHADER_VERBOSE_GLES30("Generated VERTEX program:\n%s\n---\n", src.str().c_str());
+
+ return src.str().c_str();
+}
+
+// --- FRAGMENT program ----------------------------------------------------------------------------
+
+
+static void DecodeTextureCombinerDescriptor (unsigned int combinerDesc,
+ combiner::Operation& operation,
+ combiner::Source sources[3],
+ combiner::Operand operands[3],
+ int& scale)
+{
+ int srcBits0 = (combinerDesc >> combiner::kSrcZeroShift) & 0xFF;
+ int srcBits1 = combinerDesc & 0xFF;
+ int cf = COMBINER_GET_FUNC(combinerDesc);
+
+ sources[0] = static_cast<combiner::Source>(srcBits0 & combiner::kSourceMask);
+ operands[0] = static_cast<combiner::Operand>(srcBits0 >> combiner::kOperandShift);
+ sources[1] = static_cast<combiner::Source>(srcBits1 & combiner::kSourceMask);
+ operands[1] = static_cast<combiner::Operand>(srcBits1 >> combiner::kOperandShift);
+ scale = (combinerDesc >> combiner::kScaleShift);
+
+ if (cf & combiner::kBlendFuncMask)
+ {
+ int blendF = COMBINER_GET_BLEND_FUNC_INDEX(cf);
+ int src2 = cf & combiner::kSourceMask;
+ int oper2 = ((cf & combiner::kOperandTwo) >> combiner::kOperandShift) | 1;
+
+ switch( blendF )
+ {
+ case 1:
+ // src0 * src2 alpha + src1
+ sources[2] = static_cast<combiner::Source>(src2);
+ operands[2] = static_cast<combiner::Operand>(oper2);
+ operation = combiner::kOpMulAdd;
+ break;
+ case 3:
+ // src0 * src2 alpha - src1
+ sources[2] = static_cast<combiner::Source>(src2);
+ operands[2] = static_cast<combiner::Operand>(oper2);
+ operation = combiner::kOpMulSub;
+ break;
+ // If not supported or lerp combiner, must go below
+ case 0:
+ // src0 lerp(src2 alpha) src1
+ // handeled by default
+ case 2:
+ // src0 * src2 alpha +- src1
+ // not supported!
+ default:
+ if (blendF != 0) ErrorString("Combiner function not supported by OpenGLES, defaulting to LERP!");
+ sources[2] = static_cast<combiner::Source>(src2);
+ operands[2] = static_cast<combiner::Operand>(oper2);
+ operation = combiner::kOpLerp;
+ break;
+ }
+
+
+ }
+ else
+ operation = static_cast<combiner::Operation>(cf);
+}
+
+static void AddTexOperandSrc (
+ std::ostringstream& src,
+ int unitNdx,
+ combiner::Channels channels,
+ combiner::Operand operand,
+ combiner::Source source)
+{
+ src << "(";
+
+ if (operand == combiner::kOperOneMinusSrcAlpha ||
+ operand == combiner::kOperOneMinusSrcColor)
+ {
+ src << "vec4(1.0) - ";
+ }
+
+ switch (source)
+ {
+ case combiner::kSrcPrimaryColor:
+ src << "v_color";
+ break;
+
+ case combiner::kSrcPrevious:
+ src << "prev";
+ break;
+
+ case combiner::kSrcTexture:
+ src << "texture";
+ break;
+
+ case combiner::kSrcConstant:
+ src << "_glesTextureEnvColor" << unitNdx;
+ break;
+
+ default:
+ printf_console("Error: Unsupported combiner source %d\n", source);
+ src << "vec4(1.0)"; /* Dummy value. */
+ }
+
+ src << ")";
+
+ switch (operand)
+ {
+ case combiner::kOperSrcColor:
+ case combiner::kOperOneMinusSrcColor:
+ if (channels == combiner::kRGBA)
+ src << ".rgba";
+ else if (channels == combiner::kRGB)
+ src << ".rgb";
+ else if (channels == combiner::kAlpha)
+ src << ".a";
+ break;
+
+ case combiner::kOperSrcAlpha:
+ case combiner::kOperOneMinusSrcAlpha:
+ if (channels == combiner::kRGBA)
+ src << ".aaaa";
+ else if (channels == combiner::kRGB)
+ src << ".aaa";
+ else if (channels == combiner::kAlpha)
+ src << ".a";
+ break;
+ }
+}
+
+static void AddTextureCombinerBody (std::ostringstream& src, int unitNdx, UInt32 combinerDesc, combiner::Channels channels)
+{
+ Assert(combiner::kRGBA == 0);
+ Assert(combiner::kRGB == 1);
+ Assert(combiner::kAlpha == 2);
+
+ const std::string channelTypes[] = { "vec4", "vec3", "float" };
+ const std::string channelMask[] = { "", ".rgb", ".a" };
+
+
+ combiner::Source sources[3];
+ combiner::Operand operands[3];
+ combiner::Operation op;
+ int scale;
+
+ DecodeTextureCombinerDescriptor(combinerDesc,
+ op, sources, operands, scale);
+
+ if ((op == combiner::kOpDot3RGBA || op == combiner::kOpDot3RGB))
+ {
+ if (channels == combiner::kAlpha)
+ return;
+ channels = combiner::kRGBA;
+ }
+
+ src << " color" << channelMask[channels] << " = ";
+
+ switch (op)
+ {
+ case combiner::kOpReplace:
+ AddTexOperandSrc(src, unitNdx, channels, operands[0], sources[0]);
+ break;
+
+ case combiner::kOpModulate:
+ {
+ AddTexOperandSrc(src, unitNdx, channels, operands[0], sources[0]);
+ src << " * ";
+ AddTexOperandSrc(src, unitNdx, channels, operands[1], sources[1]);
+ break;
+ }
+
+ case combiner::kOpAdd:
+ {
+ src << "(";
+ AddTexOperandSrc(src, unitNdx, channels, operands[0], sources[0]);
+ src << " + ";
+ AddTexOperandSrc(src, unitNdx, channels, operands[1], sources[1]);
+ src << ")";
+ break;
+ }
+
+ case combiner::kOpSubtract:
+ {
+ src << "(";
+ AddTexOperandSrc(src, unitNdx, channels, operands[0], sources[0]);
+ src << " - ";
+ AddTexOperandSrc(src, unitNdx, channels, operands[1], sources[1]);
+ src << ")";
+ break;
+ }
+
+ case combiner::kOpAddSigned:
+ {
+ src << "(";
+ AddTexOperandSrc(src, unitNdx, channels, operands[0], sources[0]);
+ src << " + ";
+ AddTexOperandSrc(src, unitNdx, channels, operands[1], sources[1]);
+ src << " - " << channelTypes[channels] << "(0.5)";
+ src << ")";
+ break;
+ }
+
+ case combiner::kOpLerp:
+ {
+ // NOTE: arguments of Unity LERP combiner are reversed for some reason
+ src << "mix(";
+ AddTexOperandSrc(src, unitNdx, channels, operands[1], sources[1]);
+ src << ", ";
+ AddTexOperandSrc(src, unitNdx, channels, operands[0], sources[0]);
+ src << ", ";
+ AddTexOperandSrc(src, unitNdx, combiner::kAlpha, operands[2], sources[2]);
+ src << ")";
+ break;
+ }
+
+ case combiner::kOpDot3RGB:
+ {
+ if (channels == combiner::kRGBA)
+ {
+ src << channelTypes[channels] << "(vec3(4.0 * dot(";
+ AddTexOperandSrc(src, unitNdx, combiner::kRGB, operands[0], sources[0]);
+ src << " - vec3(0.5), ";
+ AddTexOperandSrc(src, unitNdx, combiner::kRGB, operands[1], sources[1]);
+ src << " - vec3(0.5))), ";
+ AddTexOperandSrc(src, unitNdx, combiner::kAlpha, operands[0], sources[0]);
+ src << ")";
+ // Note: I am really not sure what goes into alpha channel when dot3_rgb is performed, it's definetly not the from dot
+ // My best guess, that original alpha value is kept
+
+ }
+ else
+ {
+ src << channelTypes[channels] << "(4.0* dot(";
+ AddTexOperandSrc(src, unitNdx, combiner::kRGB, operands[0], sources[0]);
+ src << " - vec3(0.5), ";
+ AddTexOperandSrc(src, unitNdx, combiner::kRGB, operands[1], sources[1]);
+ src << " - vec3(0.5)))";
+ }
+ break;
+ }
+
+ case combiner::kOpDot3RGBA:
+ {
+ src << channelTypes[channels] << "(4.0 * dot(";
+ AddTexOperandSrc(src, unitNdx, combiner::kRGB, operands[0], sources[0]);
+ src << " - vec3(0.5), ";
+ AddTexOperandSrc(src, unitNdx, combiner::kRGB, operands[1], sources[1]);
+ src << " - vec3(0.5)))";
+ break;
+ }
+ case combiner::kOpMulAdd:
+ {
+ src << "(";
+ AddTexOperandSrc(src, unitNdx, channels, operands[0], sources[0]);
+ src << " * ";
+ AddTexOperandSrc(src, unitNdx, channels, operands[2], sources[2]);
+ src << " + ";
+ AddTexOperandSrc(src, unitNdx, channels, operands[1], sources[1]);
+ src << ")";
+ break;
+ }
+ case combiner::kOpMulSub:
+ {
+ src << "(";
+ AddTexOperandSrc(src, unitNdx, channels, operands[0], sources[0]);
+ src << " * ";
+ AddTexOperandSrc(src, unitNdx, channels, operands[2], sources[2]);
+ src << " - ";
+ AddTexOperandSrc(src, unitNdx, channels, operands[1], sources[1]);
+ src << ")";
+ break;
+ }
+ default:
+ ErrorString(Format("Error: Unsupported combiner operation %d\n", op).c_str());
+
+ /* Dummy value. */
+ src << channelTypes[channels] << "(1.0)";
+ break;
+ }
+
+ if (scale != 1)
+ src << " * float(" << scale << ".0)";
+ src << ";\n";
+}
+
+static void AddTextureCombinerSrc (std::ostringstream& src, int unitNdx, bool isCube, UInt32 colorCombiner, UInt32 alphaCombiner)
+{
+ src << " {\n /* Combiner " << unitNdx << " */\n";
+
+ /* Perform lookup. */
+ src << " lowp vec4 texture = " << "texture(u_sampler" << unitNdx << ", v_texCoord" << unitNdx << ");\n";
+
+ src << " lowp vec4 prev = " << ((unitNdx > 0)? "color": "v_color") << ";\n";
+
+ /* Combine. */
+ if (colorCombiner == alphaCombiner)
+ {
+ // In case of color and alpha combiner being the same
+ // we calc all 4 channels in a single operation
+ // as some GLSL compilers (iPhone) will silently fail on following:
+ // color.rgb = arg0.rgb (op) arg1.rgb
+ // color.a = arg0.a (op) arg1.a
+ // instead we spit:
+ // color = arg0 (op) arg1
+ // plus it is more readable
+ AddTextureCombinerBody(src, unitNdx, colorCombiner, combiner::kRGBA);
+ }
+ else
+ {
+ AddTextureCombinerBody(src, unitNdx, colorCombiner, combiner::kRGB);
+ AddTextureCombinerBody(src, unitNdx, alphaCombiner, combiner::kAlpha);
+ }
+
+ src << " }\n";
+}
+
+
+std::string BuildFragmentShaderSourceGLES30 (const FixedFunctionStateGLES30& state)
+{
+ DBG_SHADER_VERBOSE_GLES30("ShaderGeneratorGLES30::BuildFragmentShaderSource()\n");
+ DBG_SHADER_VERBOSE_GLES30(" state: %s\n", state.ToString().c_str());
+
+ std::ostringstream src;
+
+ src << "#version 300 es\n";
+
+ bool alphaTestEnabled = state.alphaTest != kFuncDisabled &&
+ state.alphaTest != kFuncAlways;
+
+ /* Default varyings. */
+ src << "in lowp vec4 v_color;\n";
+
+ /* Uniforms. */
+ if (alphaTestEnabled)
+ src << "uniform lowp float _glesAlphaTestReference;\n";
+
+ if (state.fogMode > kFogDisabled)
+ {
+ src << "in lowp vec4 _glesFogColorPreMul;\n";
+ src << "in lowp vec4 _glesFogVar;\n";
+ }
+
+ /* Texture units. */
+ for (int i = 0; i < state.texUnitCount; i++)
+ {
+ if (!state.texUnitCube[i])
+ {
+ if (state.texUnitGen[i] == kTexGenObject)
+ src << "in highp vec4 v_texGenObjCoord" << i << ";\n";
+ else
+ src << "in mediump vec2 v_texCoord" << i << ";\n";
+
+ src << "uniform sampler2D u_sampler" << i << ";\n";
+ }
+ else
+ {
+ src << "in highp vec3 v_texCoord" << i << ";\n";
+ src << "uniform samplerCube u_sampler" << i << ";\n";
+ }
+
+ src << "uniform lowp vec4 _glesTextureEnvColor" << i << ";\n";
+ }
+
+
+ /* Main body. */
+ src << "out lowp vec4 _glesFragColor;\n";
+ src << "\nvoid main()\n";
+ src << "{\n";
+
+ /* Initialize color. */
+ src << " lowp vec4 color = v_color;\n";
+
+ /* Generate correct texCoords if we have texGenObject */
+ for (int i = 0; i < state.texUnitCount; i++)
+ {
+ if (!state.texUnitCube[i] && state.texUnitGen[i] == kTexGenObject)
+ src << " highp vec2 v_texCoord" << i << " = v_texGenObjCoord" << i << ".xy / v_texGenObjCoord" << i << ".w;\n";
+ }
+
+ /* Texturing. */
+ for (int i = 0; i < state.texUnitCount; i++)
+ AddTextureCombinerSrc(src, i, state.texUnitCube[i], state.texUnitColorCombiner[i], state.texUnitAlphaCombiner[i]);
+
+ if (state.fogMode > kFogDisabled)
+ src << " _glesFragColor = color * _glesFogVar + _glesFogColorPreMul;\n";
+ else
+ src << " _glesFragColor = color;\n";
+
+ /* Alpha test. */
+ if (alphaTestEnabled)
+ {
+ Assert(gGraphicsCaps.gles30.hasAlphaTestQCOM == false);
+
+ if (state.alphaTest == kFuncNever)
+ {
+ // ToDo: Do we just discard everything, or skip drawing itself at vbo level?
+ src << " discard;\n";
+ }
+ else
+ {
+ // Reverse logic because we're using here 'discard'
+ static const char* s_cmpOps[] =
+ {
+ "", // kFuncDisabled
+ "", // kFuncNever
+ ">=", // kFuncLess
+ "!=", // kFuncEqual
+ ">", // kFuncLEqual
+ "<=", // kFuncGreater
+ "==", // kFuncNotEqual
+ "<", // kFuncGEqual
+ "", // kFuncAlways
+ };
+
+ src << " if (color.a " << s_cmpOps[state.alphaTest] << "_glesAlphaTestReference)\n";
+ src << " discard;\n";
+ }
+ }
+// src << " gl_FragColor = vec4(v_color.xyz, 1.0);\n"; // DEBUG DEBUG
+// src << " gl_FragColor = 0.5 * texture2D(u_sampler0, v_texCoord0);\n"; // DEBUG DEBUG
+// src << " gl_FragColor = vec4(_glesTextureEnvColor0.rgb, 1.0);\n"; // DEBUG DEBUG
+ src << "}\n";
+
+ DBG_SHADER_VERBOSE_GLES30("Generated FRAGMENT program:\n%s\n---\n", src.str().c_str());
+
+ return src.str().c_str();
+}
diff --git a/Runtime/GfxDevice/opengles30/ShaderGeneratorGLES30.h b/Runtime/GfxDevice/opengles30/ShaderGeneratorGLES30.h
new file mode 100644
index 0000000..57e0045
--- /dev/null
+++ b/Runtime/GfxDevice/opengles30/ShaderGeneratorGLES30.h
@@ -0,0 +1,24 @@
+#ifndef SHADERGENERATOR_GLES30_H
+#define SHADERGENERATOR_GLES30_H
+
+#include <string>
+
+class FixedFunctionStateGLES30;
+
+std::string BuildVertexShaderSourceGLES30 (const FixedFunctionStateGLES30& state);
+std::string BuildFragmentShaderSourceGLES30 (const FixedFunctionStateGLES30& state);
+
+struct FullStateCompareGLES30
+{
+ bool operator() (FixedFunctionStateGLES30 const& a, FixedFunctionStateGLES30 const& b) const;
+};
+struct VertexStateCompareGLES30
+{
+ bool operator() (FixedFunctionStateGLES30 const& a, FixedFunctionStateGLES30 const& b) const;
+};
+struct FragmentStateCompareGLES30
+{
+ bool operator() (FixedFunctionStateGLES30 const& a, FixedFunctionStateGLES30 const& b) const;
+};
+
+#endif /* SHADERGENERATOR_GLES30_H */
diff --git a/Runtime/GfxDevice/opengles30/TextureIdMapGLES30.h b/Runtime/GfxDevice/opengles30/TextureIdMapGLES30.h
new file mode 100644
index 0000000..b0bcd2e
--- /dev/null
+++ b/Runtime/GfxDevice/opengles30/TextureIdMapGLES30.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#include "Configuration/UnityConfigure.h"
+#include "Runtime/GfxDevice/TextureIdMap.h"
+#include "IncludesGLES30.h"
+
+inline GLuint TextureIdMapGLES30_QueryOrCreate(TextureID texid)
+{
+ GLuint ret = (GLuint)TextureIdMap::QueryNativeTexture(texid);
+ if(ret == 0)
+ {
+ GLES_CHK(glGenTextures(1, &ret));
+ TextureIdMap::UpdateTexture(texid, ret);
+ }
+
+ return ret;
+}
diff --git a/Runtime/GfxDevice/opengles30/TexturesGLES30.cpp b/Runtime/GfxDevice/opengles30/TexturesGLES30.cpp
new file mode 100644
index 0000000..ba3885c
--- /dev/null
+++ b/Runtime/GfxDevice/opengles30/TexturesGLES30.cpp
@@ -0,0 +1,535 @@
+#include "UnityPrefix.h"
+#include "TexturesGLES30.h"
+#include "Runtime/GfxDevice/GfxDeviceTypes.h"
+#include "Runtime/Graphics/Image.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+#include "Runtime/GfxDevice/TextureUploadUtils.h"
+#include "Runtime/Graphics/S3Decompression.h"
+#include "Runtime/Graphics/Texture2D.h"
+#include "Runtime/Shaders/GraphicsCaps.h"
+#include "Runtime/Utilities/BitUtility.h"
+#include "IncludesGLES30.h"
+#include "AssertGLES30.h"
+#include "DebugGLES30.h"
+#include "TextureIdMapGLES30.h"
+
+#include <vector>
+
+#if GFX_SUPPORTS_OPENGLES30
+
+struct TextureFormatInfoGLES30
+{
+ enum Type
+ {
+ kTypeReserved = 0, //!< Reserved format (should not be used).
+ kTypeUncompressed, //!< Uncompressed, natively supported format.
+ kTypeNonNative, //!< Uncompressed, but not supported format. Requires swizzle to nativeFormat.
+ kTypeCompressed //!< Compressed format. If not supported, always decompressed to kTexFormatRGBA32
+ };
+
+ Type type; //!< Texture format type, as interpreted by ES3.
+
+ UInt32 linearInternalFormat; //!< Internal format for linear color space
+ UInt32 sRGBInternalFormat; //!< Internal format for sRGB color space or 0 if not supported
+
+ UInt32 transferFormat; //!< Transfer format, or 0 if compressed texture
+ UInt32 dataType; //!< Transfer data type, or 0 if compressed texture
+
+ TextureFormat nativeFormat; //!< Data must be converted to this format first.
+};
+
+static const TextureFormatInfoGLES30 s_textureFormatInfos[] =
+{
+#define _RES { TextureFormatInfoGLES30::kTypeReserved, 0, 0, 0, 0, 0 }
+#define _NAT(NATIVE) { TextureFormatInfoGLES30::kTypeNonNative, 0, 0, 0, 0, NATIVE }
+#define _UNC(LINEAR, TRANSFERFMT, DATATYPE) { TextureFormatInfoGLES30::kTypeUncompressed, LINEAR, 0, TRANSFERFMT, DATATYPE, 0 }
+#define _SRG(LINEAR, SRGB, TRANSFERFMT, DATATYPE) { TextureFormatInfoGLES30::kTypeUncompressed, LINEAR, SRGB, TRANSFERFMT, DATATYPE, 0 }
+#define _CMP(LINEAR, SRGB) { TextureFormatInfoGLES30::kTypeCompressed, LINEAR, SRGB, 0, 0, 0 }
+
+ /* 0 */ _RES,
+ /* kTexFormatAlpha8 */ _UNC(GL_ALPHA, GL_ALPHA, GL_UNSIGNED_BYTE),
+ /* kTexFormatARGB4444 */ _UNC(GL_RGBA4, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4), // \todo [2013-05-03 pyry] Should we swizzle this?
+ /* kTexFormatRGB24 */ _SRG(GL_RGB8, GL_SRGB8, GL_RGB, GL_UNSIGNED_BYTE),
+ /* kTexFormatRGBA32 */ _SRG(GL_RGBA8, GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE),
+ /* kTexFormatARGB32 */ _NAT(kTexFormatRGBA32),
+ /* kTexFormatARGBFloat */ _UNC(GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT),
+ /* kTexFormatRGB565 */ _UNC(GL_RGB565, GL_RGB, GL_UNSIGNED_SHORT_5_6_5),
+ /* kTexFormatBGR24 */ _NAT(kTexFormatRGB24),
+ /* kTexFormatAlphaLum16 */ _UNC(GL_LUMINANCE_ALPHA, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE),
+ /* kTexFormatDXT1 */ _CMP(GL_COMPRESSED_RGB_S3TC_DXT1_EXT, GL_COMPRESSED_SRGB_S3TC_DXT1_NV),
+ /* kTexFormatDXT3 */ _CMP(GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_NV),
+ /* kTexFormatDXT5 */ _CMP(GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_NV),
+ /* kTexFormatRGBA4444 */ _UNC(GL_RGBA4, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4),
+ /* kTexReserved1 */ _RES,
+ /* kTexReserved2 */ _RES,
+ /* kTexReserved3 */ _RES,
+ /* kTexReserved4 */ _RES,
+ /* kTexReserved5 */ _RES,
+ /* kTexReserved6 */ _RES,
+ /* reserved (wii) 0 */ _RES,
+ /* reserved (wii) 1 */ _RES,
+ /* reserved (wii) 2 */ _RES,
+ /* reserved (wii) 3 */ _RES,
+ /* reserved (wii) 4 */ _RES,
+ /* reserved (wii) 5 */ _RES,
+ /* reserved (wii) 6 */ _RES,
+ /* reserved (wii) 7 */ _RES,
+ /* kTexReserved11 */ _RES,
+ /* kTexReserved12 */ _RES,
+ /* kTexFormatPVRTC_RGB2 */ _CMP(GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG, 0),
+ /* kTexFormatPVRTC_RGBA2 */ _CMP(GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG, 0),
+ /* kTexFormatPVRTC_RGB4 */ _CMP(GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG, 0),
+ /* kTexFormatPVRTC_RGBA4 */ _CMP(GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG, 0),
+ /* kTexFormatETC_RGB4 */ _CMP(GL_COMPRESSED_RGB8_ETC2, 0),
+ /* kTexFormatATC_RGB4 */ _CMP(GL_ATC_RGB_AMD, 0),
+ /* kTexFormatATC_RGBA8 */ _CMP(GL_ATC_RGBA_INTERPOLATED_ALPHA_AMD, 0),
+ /* kTexFormatBGRA32 */ _NAT(kTexFormatRGBA32), // \todo [2013-05-03 pyry] Use BGRA extension if possible.
+ /* kTexFormatFlashATF_RGB_DXT1 */ _RES,
+ /* kTexFormatFlashATF_RGBA_JPG */ _RES,
+ /* kTexFormatFlashATF_RGB_JPG */ _RES,
+ /* kTexFormatEAC_R */ _CMP(GL_COMPRESSED_R11_EAC, 0),
+ /* kTexFormatEAC_R_SIGNED */ _CMP(GL_COMPRESSED_SIGNED_R11_EAC, 0),
+ /* kTexFormatEAC_RG */ _CMP(GL_COMPRESSED_RG11_EAC, 0),
+ /* kTexFormatEAC_RG_SIGNED */ _CMP(GL_COMPRESSED_SIGNED_RG11_EAC, 0),
+ /* kTexFormatETC2_RGB */ _CMP(GL_COMPRESSED_RGB8_ETC2, 0),
+ /* kTexFormatETC2_RGBA1 */ _CMP(GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2, 0),
+ /* kTexFormatETC2_RGBA8 */ _CMP(GL_COMPRESSED_RGBA8_ETC2_EAC, 0),
+ /*kTexFormatASTC_RGB_4x4 */ _CMP(GL_COMPRESSED_RGBA_ASTC_4x4_KHR, 0),
+ /*kTexFormatASTC_RGB_5x5 */ _CMP(GL_COMPRESSED_RGBA_ASTC_5x5_KHR, 0),
+ /*kTexFormatASTC_RGB_6x6 */ _CMP(GL_COMPRESSED_RGBA_ASTC_6x6_KHR, 0),
+ /*kTexFormatASTC_RGB_8x8 */ _CMP(GL_COMPRESSED_RGBA_ASTC_8x8_KHR, 0),
+ /*kTexFormatASTC_RGB_10x10 */ _CMP(GL_COMPRESSED_RGBA_ASTC_10x10_KHR, 0),
+ /*kTexFormatASTC_RGB_12x12 */ _CMP(GL_COMPRESSED_RGBA_ASTC_12x12_KHR, 0),
+
+ /*kTexFormatASTC_RGBA_4x4 */ _CMP(GL_COMPRESSED_RGBA_ASTC_4x4_KHR, 0),
+ /*kTexFormatASTC_RGBA_5x5 */ _CMP(GL_COMPRESSED_RGBA_ASTC_5x5_KHR, 0),
+ /*kTexFormatASTC_RGBA_6x6 */ _CMP(GL_COMPRESSED_RGBA_ASTC_6x6_KHR, 0),
+ /*kTexFormatASTC_RGBA_8x8 */ _CMP(GL_COMPRESSED_RGBA_ASTC_8x8_KHR, 0),
+ /*kTexFormatASTC_RGBA_10x10 */ _CMP(GL_COMPRESSED_RGBA_ASTC_10x10_KHR, 0),
+ /*kTexFormatASTC_RGBA_12x12 */ _CMP(GL_COMPRESSED_RGBA_ASTC_12x12_KHR, 0),
+
+#undef _CMP
+#undef _SRG
+#undef _NAT
+#undef _UNC
+#undef _RES
+};
+
+// Static assert for array size.
+typedef char __gles3TexFormatTableSizeAssert[(int)(sizeof(s_textureFormatInfos) / sizeof(s_textureFormatInfos[0])) == kTexFormatTotalCount ? 1 : - 1];
+
+static const TextureFormatInfoGLES30& GetTextureFormatInfoGLES30 (TextureFormat format)
+{
+ Assert(0 <= format && format < (int)(sizeof(s_textureFormatInfos)/sizeof(s_textureFormatInfos[0])));
+ return s_textureFormatInfos[format];
+}
+
+static inline bool IsTextureFormatSupported (TextureFormat format)
+{
+ return gGraphicsCaps.supportsTextureFormat[format];
+}
+
+static const char* GetCompressedTextureFormatName (TextureFormat format)
+{
+ if (IsCompressedPVRTCTextureFormat(format))
+ return "PVRTC";
+ else if (IsCompressedDXTTextureFormat(format))
+ return "DXT";
+ else if (IsCompressedETCTextureFormat(format))
+ return "ETC1";
+ else if (IsCompressedATCTextureFormat(format))
+ return "ATC";
+ else if (IsCompressedETC2TextureFormat(format))
+ return "ETC2";
+ else if (IsCompressedEACTextureFormat(format))
+ return "EAC";
+ else
+ return "UNKNOWN";
+}
+
+static void GetDecompressBlockSize (TextureFormat format, int& width, int& height)
+{
+ // \todo [2013-05-06 pyry] Is there any format that doesn't use 4x4 blocks?
+ width = 4;
+ height = 4;
+}
+
+static inline int GetDecompressBlockWidth (TextureFormat format)
+{
+ int w, h;
+ GetDecompressBlockSize(format, w, h);
+ return w;
+}
+
+static inline int GetDecompressBlockHeight (TextureFormat format)
+{
+ int w, h;
+ GetDecompressBlockSize(format, w, h);
+ return h;
+}
+
+static inline int AlignToBlockSize (int dim, int blockSize)
+{
+ if (dim % blockSize != 0)
+ return dim + blockSize - (dim % blockSize);
+ else
+ return dim;
+}
+
+static int UploadPyramidCompressed (UInt32 target,
+ TextureFormat format,
+ bool sRGB,
+ int width,
+ int height,
+ int numLevels,
+ const UInt8* data)
+{
+ Assert(IsTextureFormatSupported(format));
+
+ const TextureFormatInfoGLES30& formatInfo = GetTextureFormatInfoGLES30(format);
+ const bool isSRGBUploadOk = formatInfo.sRGBInternalFormat != 0;
+ const UInt32 internalFormat = (sRGB && isSRGBUploadOk) ? formatInfo.sRGBInternalFormat : formatInfo.linearInternalFormat;
+ const UInt8* curDataPtr = data;
+ int totalUploadSize = 0;
+
+ for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+ {
+ const int levelW = std::max<int>(width>>levelNdx, 1);
+ const int levelH = std::max<int>(height>>levelNdx, 1);
+ const int levelSize = CalculateImageSize(levelW, levelH, format);
+ const int uploadLevel = levelNdx;
+
+ GLES_CHK(glCompressedTexImage2D(target, levelNdx, internalFormat, levelW, levelH, 0, levelSize, curDataPtr));
+
+ curDataPtr += levelSize;
+ totalUploadSize += levelSize;
+ }
+
+ return totalUploadSize;
+}
+
+static int UploadPyramidDecompress (UInt32 target,
+ TextureFormat format,
+ bool sRGB,
+ int width,
+ int height,
+ int numLevels,
+ const UInt8* data)
+{
+ const TextureFormatInfoGLES30& formatInfo = GetTextureFormatInfoGLES30(format);
+ const TextureFormat decompressFormat = kTexFormatRGBA32;
+ const TextureFormatInfoGLES30& uploadFormat = GetTextureFormatInfoGLES30(decompressFormat);
+ const bool isSRGBUploadOk = uploadFormat.sRGBInternalFormat != 0;
+ const UInt32 internalFormat = (sRGB && isSRGBUploadOk) ? uploadFormat.sRGBInternalFormat : uploadFormat.linearInternalFormat;
+ const UInt8* curDataPtr = data;
+ const int blockW = GetDecompressBlockWidth(format);
+ const int blockH = GetDecompressBlockHeight(format);
+ int totalUploadSize = 0;
+ std::vector<UInt8> decompressBuffer (CalculateImageSize(AlignToBlockSize(width, blockW), AlignToBlockSize(height, blockH), decompressFormat));
+
+ for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+ {
+ const int levelW = std::max<int>(width>>levelNdx, 1);
+ const int levelH = std::max<int>(height>>levelNdx, 1);
+ const int levelSize = CalculateImageSize(levelW, levelH, format);
+ const int decompressW = AlignToBlockSize(levelW, blockW);
+ const int decompressH = AlignToBlockSize(levelH, blockH);
+ const int decompressSize = CalculateImageSize(decompressW, decompressH, decompressFormat);
+
+ Assert(decompressSize <= (int)decompressBuffer.size());
+ if (!DecompressNativeTextureFormat(format, levelW, levelH, (UInt32*)curDataPtr, decompressW, decompressH, (UInt32*)&decompressBuffer[0]))
+ ErrorStringMsg("Decompressing level %d failed!", levelNdx);
+
+ GLES_CHK(glTexImage2D(target, levelNdx, internalFormat, levelW, levelH, 0, uploadFormat.transferFormat, uploadFormat.dataType, &decompressBuffer[0]));
+
+ curDataPtr += levelSize;
+ totalUploadSize += decompressSize;
+ }
+
+ return totalUploadSize;
+}
+
+static int UploadPyramidConvert (UInt32 target,
+ TextureFormat srcFormat,
+ TextureFormat dstFormat,
+ bool sRGB,
+ int width,
+ int height,
+ int numLevels,
+ const UInt8* data)
+{
+ const TextureFormatInfoGLES30& dstFormatInfo = GetTextureFormatInfoGLES30(dstFormat);
+ const bool isSRGBUploadOk = dstFormatInfo.sRGBInternalFormat != 0;
+ const UInt32 internalFormat = (sRGB && isSRGBUploadOk) ? dstFormatInfo.sRGBInternalFormat : dstFormatInfo.linearInternalFormat;
+ const UInt8* curDataPtr = data;
+ int totalUploadSize = 0;
+ std::vector<UInt8> convertBuffer (CalculateImageSize(width, height, dstFormat));
+
+ for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+ {
+ const int levelW = std::max<int>(width>>levelNdx, 1);
+ const int levelH = std::max<int>(height>>levelNdx, 1);
+ const int levelSize = CalculateImageSize(levelW, levelH, srcFormat);
+
+ ImageReference src (levelW, levelH, GetRowBytesFromWidthAndFormat(levelW, srcFormat), srcFormat, (UInt8*)curDataPtr);
+ ImageReference dst (levelW, levelH, GetRowBytesFromWidthAndFormat(levelW, dstFormat), dstFormat, &convertBuffer[0]);
+
+ dst.BlitImage(src);
+
+ GLES_CHK(glTexImage2D(target, levelNdx, internalFormat, levelW, levelH, 0, dstFormatInfo.transferFormat, dstFormatInfo.dataType, &convertBuffer[0]));
+
+ curDataPtr += levelSize;
+ totalUploadSize += dst.GetRowBytes()*dst.GetHeight();
+ }
+
+ return totalUploadSize;
+}
+
+static int UploadPyramid (UInt32 target,
+ TextureFormat format,
+ bool sRGB,
+ int width,
+ int height,
+ int numLevels,
+ const UInt8* data)
+{
+ const TextureFormatInfoGLES30& formatInfo = GetTextureFormatInfoGLES30(format);
+ const bool isSRGBUploadOk = formatInfo.sRGBInternalFormat != 0;
+ const UInt32 internalFormat = (sRGB && isSRGBUploadOk) ? formatInfo.sRGBInternalFormat : formatInfo.linearInternalFormat;
+ const UInt8* curDataPtr = data;
+ int totalUploadSize = 0;
+
+ for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
+ {
+ const int levelW = std::max<int>(width>>levelNdx, 1);
+ const int levelH = std::max<int>(height>>levelNdx, 1);
+ const int levelSize = CalculateImageSize(levelW, levelH, format);
+
+ GLES_CHK(glTexImage2D(target, levelNdx, internalFormat, levelW, levelH, 0, formatInfo.transferFormat, formatInfo.dataType, curDataPtr));
+
+ curDataPtr += levelSize;
+ totalUploadSize += levelSize;
+ }
+
+ return totalUploadSize;
+}
+
+void UploadTexture2DGLES3 (TextureID texID,
+ TextureDimension dimension,
+ UInt8* srcData,
+ int width,
+ int height,
+ TextureFormat format,
+ int mipCount,
+ UInt32 uploadFlags,
+ int skipMipLevels,
+ TextureColorSpace colorSpace)
+{
+ Assert(dimension == kTexDim2D); // \todo [2013-05-03 pyry] Remove parameter.
+
+ const TextureFormatInfoGLES30& formatInfo = GetTextureFormatInfoGLES30(format);
+ const bool isSRGB = colorSpace == kTexColorSpaceSRGBXenon || colorSpace == kTexColorSpaceSRGB;
+ const bool isCompressed = formatInfo.type == TextureFormatInfoGLES30::kTypeCompressed;
+ const bool decompress = isCompressed && !IsTextureFormatSupported(format);
+ const bool convertToNative = formatInfo.type == TextureFormatInfoGLES30::kTypeNonNative;
+ int totalUploadSize = 0;
+
+ Assert(!decompress || !convertToNative);
+ Assert(skipMipLevels < mipCount);
+
+ if (decompress)
+ printf_console("WARNING: %s compressed texture format not supported, decompressing!\n", GetCompressedTextureFormatName(format));
+ else if (convertToNative)
+ printf_console("WARNING: no native support for texture format %d, converting to %d!\n", format, formatInfo.nativeFormat);
+
+ // Create and bind texture.
+ TextureIdMapGLES30_QueryOrCreate(texID);
+ GetRealGfxDevice().SetTexture (kShaderFragment, 0, 0, texID, dimension, std::numeric_limits<float>::infinity());
+
+ // \todo [2013-05-03 pyry] Select unpack alignment based on pixel format
+ GLES_CHK(glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
+
+ // Upload parameters.
+ const int baseLevelW = std::max<int>(1, width >> skipMipLevels);
+ const int baseLevelH = std::max<int>(1, height >> skipMipLevels);
+ const int numLevelsUpload = mipCount-skipMipLevels;
+ const UInt8* baseLevelPtr = srcData;
+
+ // Adjust data pointer by number of levels skipped.
+ // \todo [2013-045-03 pyry] Skip levels if they are too large for HW.
+ for (int levelNdx = 0; levelNdx < skipMipLevels; levelNdx++)
+ {
+ int levelW = std::max<int>(1, width >> levelNdx);
+ int levelH = std::max<int>(1, height >> levelNdx);
+ int levelSize = CalculateImageSize(levelW, levelH, format);
+
+ baseLevelPtr += levelSize;
+ }
+
+ // Call upload.
+ if (isCompressed)
+ {
+ if (decompress)
+ totalUploadSize = UploadPyramidDecompress(GL_TEXTURE_2D, format, isSRGB, baseLevelW, baseLevelH, numLevelsUpload, baseLevelPtr);
+ else
+ totalUploadSize = UploadPyramidCompressed(GL_TEXTURE_2D, format, isSRGB, baseLevelW, baseLevelH, numLevelsUpload, baseLevelPtr);
+ }
+ else
+ {
+ if (convertToNative)
+ totalUploadSize = UploadPyramidConvert(GL_TEXTURE_2D, format, formatInfo.nativeFormat, isSRGB, baseLevelW, baseLevelH, numLevelsUpload, baseLevelPtr);
+ else
+ totalUploadSize = UploadPyramid(GL_TEXTURE_2D, format, isSRGB, baseLevelW, baseLevelH, numLevelsUpload, baseLevelPtr);
+ }
+
+ GLES_CHK(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, numLevelsUpload-1));
+
+ REGISTER_EXTERNAL_GFX_DEALLOCATION(texID.m_ID);
+ REGISTER_EXTERNAL_GFX_ALLOCATION_REF(texID.m_ID, totalUploadSize, texID.m_ID);
+}
+
+void UploadTextureSubData2DGLES3 (TextureID texID,
+ UInt8* srcData,
+ int mipLevel,
+ int x,
+ int y,
+ int width,
+ int height,
+ TextureFormat format,
+ TextureColorSpace colorSpace)
+{
+ const TextureFormatInfoGLES30& formatInfo = GetTextureFormatInfoGLES30(format);
+ const bool isSRGB = colorSpace == kTexColorSpaceSRGBXenon || colorSpace == kTexColorSpaceSRGB;
+ const bool isCompressed = formatInfo.type == TextureFormatInfoGLES30::kTypeCompressed;
+ const bool decompress = isCompressed && !IsTextureFormatSupported(format);
+ const bool convertToNative = formatInfo.type == TextureFormatInfoGLES30::kTypeNonNative;
+ const TextureFormatInfoGLES30& uploadInfo = convertToNative ? GetTextureFormatInfoGLES30(formatInfo.nativeFormat) : formatInfo;
+ int totalUploadSize = 0;
+ const UInt32 target = GL_TEXTURE_2D;
+
+ Assert(!decompress || !convertToNative);
+
+ if (decompress)
+ printf_console("WARNING: %s compressed texture format not supported, decompressing!\n", GetCompressedTextureFormatName(format));
+ else if (convertToNative)
+ printf_console("WARNING: no native support for texture format %d, converting to %d!\n", format, formatInfo.nativeFormat);
+
+ // Create and bind texture.
+ TextureIdMapGLES30_QueryOrCreate(texID);
+ GetRealGfxDevice().SetTexture (kShaderFragment, 0, 0, texID, kTexDim2D, std::numeric_limits<float>::infinity());
+
+ // \todo [2013-05-03 pyry] Select unpack alignment based on pixel format
+ GLES_CHK(glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
+
+ if (isCompressed)
+ {
+ if (decompress)
+ {
+ const int blockW = GetDecompressBlockWidth(format);
+ const int blockH = GetDecompressBlockHeight(format);
+ const int decompressW = AlignToBlockSize(width, blockW);
+ const int decompressH = AlignToBlockSize(height, blockH);
+ std::vector<UInt8> decompressBuffer (CalculateImageSize(decompressW, decompressH, format));
+
+ if (!DecompressNativeTextureFormat(format, width, height, (const UInt32*)srcData, decompressW, decompressH, (UInt32*)&decompressBuffer[0]))
+ ErrorString("Decompressing texture data failed!");
+
+ GLES_CHK(glTexSubImage2D(target, mipLevel, x, y, width, height, uploadInfo.transferFormat, uploadInfo.dataType, &decompressBuffer[0]));
+ }
+ else
+ {
+ const int dataSize = CalculateImageSize(width, height, format);
+ GLES_CHK(glCompressedTexSubImage2D(target, mipLevel, x, y, width, height, uploadInfo.linearInternalFormat, dataSize, srcData));
+ }
+ }
+ else
+ {
+ if (convertToNative)
+ {
+ std::vector<UInt8> convertBuffer (CalculateImageSize(width, height, formatInfo.nativeFormat));
+ ImageReference src (width, height, GetRowBytesFromWidthAndFormat(width, format), format, srcData);
+ ImageReference dst (width, height, GetRowBytesFromWidthAndFormat(width, formatInfo.nativeFormat), formatInfo.nativeFormat, &convertBuffer[0]);
+
+ dst.BlitImage(src);
+
+ GLES_CHK(glTexSubImage2D(target, mipLevel, x, y, width, height, uploadInfo.transferFormat, uploadInfo.dataType, &convertBuffer[0]));
+ }
+ else
+ GLES_CHK(glTexSubImage2D(target, mipLevel, x, y, width, height, uploadInfo.transferFormat, uploadInfo.dataType, srcData));
+ }
+}
+
+void UploadTextureCubeGLES3 (TextureID texID,
+ UInt8* srcData,
+ int faceDataSize,
+ int size,
+ TextureFormat format,
+ int mipCount,
+ UInt32 uploadFlags,
+ TextureColorSpace colorSpace)
+{
+ const TextureFormatInfoGLES30& formatInfo = GetTextureFormatInfoGLES30(format);
+ const bool isSRGB = colorSpace == kTexColorSpaceSRGBXenon || colorSpace == kTexColorSpaceSRGB;
+ const bool isCompressed = formatInfo.type == TextureFormatInfoGLES30::kTypeCompressed;
+ const bool decompress = isCompressed && !IsTextureFormatSupported(format);
+ const bool convertToNative = formatInfo.type == TextureFormatInfoGLES30::kTypeNonNative;
+ int totalUploadSize = 0;
+
+ Assert(!decompress || !convertToNative);
+
+ if (decompress)
+ printf_console("WARNING: %s compressed texture format not supported, decompressing!\n", GetCompressedTextureFormatName(format));
+ else if (convertToNative)
+ printf_console("WARNING: no native support for texture format %d, converting to %d!\n", format, formatInfo.nativeFormat);
+
+ // Create and bind texture.
+ TextureIdMapGLES30_QueryOrCreate(texID);
+ GetRealGfxDevice().SetTexture (kShaderFragment, 0, 0, texID, kTexDimCUBE, std::numeric_limits<float>::infinity());
+
+ // \todo [2013-05-03 pyry] Select unpack alignment based on pixel format
+ GLES_CHK(glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
+
+ // \todo [2013-045-03 pyry] Skip levels if they are too large for HW.
+ const int baseLevelOffset = 0;
+ const int baseLevelW = size;
+ const int baseLevelH = size;
+ const int numLevelsUpload = mipCount;
+
+ const GLenum faces[6] =
+ {
+ GL_TEXTURE_CUBE_MAP_POSITIVE_X,
+ GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
+ GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
+ GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
+ GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
+ GL_TEXTURE_CUBE_MAP_NEGATIVE_Z
+ };
+
+ for (int faceNdx = 0; faceNdx < 6; faceNdx++)
+ {
+ const UInt8* facePtr = (srcData + faceNdx*faceDataSize) + baseLevelOffset;
+ const UInt32 target = faces[faceNdx];
+
+ if (isCompressed)
+ {
+ if (decompress)
+ totalUploadSize = UploadPyramidDecompress(target, format, isSRGB, baseLevelW, baseLevelH, numLevelsUpload, facePtr);
+ else
+ totalUploadSize = UploadPyramidCompressed(target, format, isSRGB, baseLevelW, baseLevelH, numLevelsUpload, facePtr);
+ }
+ else
+ {
+ if (convertToNative)
+ totalUploadSize = UploadPyramidConvert(target, format, formatInfo.nativeFormat, isSRGB, baseLevelW, baseLevelH, numLevelsUpload, facePtr);
+ else
+ totalUploadSize = UploadPyramid(target, format, isSRGB, baseLevelW, baseLevelH, numLevelsUpload, facePtr);
+ }
+ }
+
+ GLES_CHK(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_LEVEL, numLevelsUpload-1));
+
+ REGISTER_EXTERNAL_GFX_DEALLOCATION(texID.m_ID);
+ REGISTER_EXTERNAL_GFX_ALLOCATION_REF(texID.m_ID, totalUploadSize, texID.m_ID);
+}
+
+#endif // GFX_SUPPORTS_OPENGLES30
diff --git a/Runtime/GfxDevice/opengles30/TexturesGLES30.h b/Runtime/GfxDevice/opengles30/TexturesGLES30.h
new file mode 100644
index 0000000..94fbb31
--- /dev/null
+++ b/Runtime/GfxDevice/opengles30/TexturesGLES30.h
@@ -0,0 +1,22 @@
+#ifndef UNITY_TEXTURES_GLES_H_
+#define UNITY_TEXTURES_GLES_H_
+
+#include "Configuration/UnityConfigure.h"
+#include "Runtime/Graphics/TextureFormat.h"
+#include "Runtime/GfxDevice/GfxDeviceTypes.h"
+
+class ImageReference;
+
+// \todo [2013-05-03 pyry] Clean up this interface.
+
+void UploadTexture2DGLES3(
+ TextureID glname, TextureDimension dimension, UInt8* srcData, int width, int height,
+ TextureFormat format, int mipCount, UInt32 uploadFlags, int masterTextureLimit, TextureColorSpace colorSpace );
+void UploadTextureSubData2DGLES3(
+ TextureID glname, UInt8* srcData,
+ int mipLevel, int x, int y, int width, int height, TextureFormat format, TextureColorSpace colorSpace );
+void UploadTextureCubeGLES3(
+ TextureID tid, UInt8* srcData, int faceDataSize, int size,
+ TextureFormat format, int mipCount, UInt32 uploadFlags, TextureColorSpace colorSpace );
+
+#endif // UNITY_TEXTURES_GLES_H_
diff --git a/Runtime/GfxDevice/opengles30/TimerQueryGLES30.cpp b/Runtime/GfxDevice/opengles30/TimerQueryGLES30.cpp
new file mode 100644
index 0000000..b59b178
--- /dev/null
+++ b/Runtime/GfxDevice/opengles30/TimerQueryGLES30.cpp
@@ -0,0 +1,100 @@
+#include "UnityPrefix.h"
+#if ENABLE_PROFILER && GFX_SUPPORTS_OPENGLES30
+#include "TimerQueryGLES30.h"
+#include "AssertGLES30.h"
+#include "UnityGLES30Ext.h"
+#include "Runtime/Shaders/GraphicsCaps.h"
+
+bool TimerQueriesGLES30::Init()
+{
+ Assert(!gGraphicsCaps.hasTimerQuery || QueryExtension("GL_NV_timer_query"));
+
+ if (gGraphicsCaps.hasTimerQuery)
+ GLES_CHK(glGenQueries(kQueriesCount, timestamp_gl));
+
+ nextIndex = 0;
+ return true;
+}
+
+void TimerQueriesGLES30::BeginTimerQueries()
+{
+ GLES_CHK(glFlush());
+ SetTimestamp();
+}
+
+void TimerQueriesGLES30::EndTimerQueries()
+{
+ GLES_CHK(glFlush());
+}
+
+unsigned TimerQueriesGLES30::SetTimestamp()
+{
+ // \todo [2013-04-17 pyry] glQueryCounter is not in ES3
+#if 0
+ GLES_CHK(gGles3ExtFunc.glQueryCounterNV(timestamp_gl[nextIndex], GL_TIMESTAMP_NV));
+#endif
+ unsigned ret = nextIndex;
+
+ ++nextIndex;
+ if(nextIndex == kQueriesCount)
+ nextIndex = 0;
+
+ return ret;
+}
+
+UInt64 TimerQueriesGLES30::GetElapsedTime(unsigned idx, bool wait)
+{
+ GLuint available = 0;
+ GLES_CHK(glGetQueryObjectuiv(timestamp_gl[idx], GL_QUERY_RESULT_AVAILABLE, &available));
+ // sometimes timestamp might be not ready (we still dont know why)
+ // the only workaround would be to add glFlush into SetTimestamp
+ // but then some timings will be a bit off
+ if(wait)
+ {
+ for(unsigned i = 0 ; i < 100 && !available ; ++i)
+ {
+ GLES_CHK(glGetQueryObjectuiv(timestamp_gl[idx], GL_QUERY_RESULT_AVAILABLE, &available));
+ }
+ }
+
+ if(available)
+ {
+ unsigned prev_idx = idx > 0 ? idx-1 : kQueriesCount-1;
+
+ // \todo [2013-04-17 pyry] i64 variant?
+ GLuint time1, time2;
+ GLES_CHK(glGetQueryObjectuiv(timestamp_gl[prev_idx], GL_QUERY_RESULT, &time1));
+ GLES_CHK(glGetQueryObjectuiv(timestamp_gl[idx], GL_QUERY_RESULT, &time2));
+
+ return time2-time1;
+ }
+
+ return kInvalidProfileTime;
+
+}
+
+
+TimerQueryGLES30::TimerQueryGLES30()
+ : m_Index(0),
+ m_Time(kInvalidProfileTime)
+{
+}
+
+void TimerQueryGLES30::Measure()
+{
+ m_Index = g_TimerQueriesGLES30.SetTimestamp();
+ m_Time = kInvalidProfileTime;
+}
+
+ProfileTimeFormat TimerQueryGLES30::GetElapsed(UInt32 flags)
+{
+ if(m_Time == kInvalidProfileTime)
+ m_Time = g_TimerQueriesGLES30.GetElapsedTime(m_Index, (flags & kWaitRenderThread) != 0);
+
+ return m_Time;
+}
+
+TimerQueriesGLES30 g_TimerQueriesGLES30;
+
+
+#endif
diff --git a/Runtime/GfxDevice/opengles30/TimerQueryGLES30.h b/Runtime/GfxDevice/opengles30/TimerQueryGLES30.h
new file mode 100644
index 0000000..9bf7e22
--- /dev/null
+++ b/Runtime/GfxDevice/opengles30/TimerQueryGLES30.h
@@ -0,0 +1,47 @@
+#ifndef TIMERQUERYGLES_H
+#define TIMERQUERYGLES_H
+
+#if ENABLE_PROFILER && GFX_SUPPORTS_OPENGLES30
+
+#include "IncludesGLES30.h"
+#include "Runtime/GfxDevice/GfxTimerQuery.h"
+
+struct TimerQueriesGLES30
+{
+ enum
+ {
+ kQueriesCount = 128,
+ };
+
+ GLuint timestamp_gl[kQueriesCount];
+ unsigned nextIndex;
+
+ bool Init();
+
+ void BeginTimerQueries();
+ void EndTimerQueries();
+
+ unsigned SetTimestamp();
+ UInt64 GetElapsedTime(unsigned idx, bool wait);
+};
+extern TimerQueriesGLES30 g_TimerQueriesGLES30;
+
+
+class TimerQueryGLES30
+ : public GfxTimerQuery
+{
+public:
+
+ TimerQueryGLES30();
+
+ virtual void Measure();
+ virtual ProfileTimeFormat GetElapsed(UInt32 flags);
+
+private:
+
+ unsigned m_Index;
+ ProfileTimeFormat m_Time;
+};
+
+#endif
+#endif
diff --git a/Runtime/GfxDevice/opengles30/TransformFeedbackSkinnedMesh.cpp b/Runtime/GfxDevice/opengles30/TransformFeedbackSkinnedMesh.cpp
new file mode 100644
index 0000000..dc22318
--- /dev/null
+++ b/Runtime/GfxDevice/opengles30/TransformFeedbackSkinnedMesh.cpp
@@ -0,0 +1,762 @@
+ #include "UnityPrefix.h"
+
+#include "Runtime/GfxDevice/opengles30/TransformFeedbackSkinnedMesh.h"
+
+#include "Runtime/Filters/Mesh/Mesh.h"
+#include "Runtime/Filters/Mesh/MeshSkinning.h"
+#include "Runtime/Filters/Renderer.h"
+#include "Runtime/GfxDevice/opengles30/VBOGLES30.h"
+#include "Runtime/GfxDevice/opengles30/AssertGLES30.h"
+#include "Runtime/Shaders/GraphicsCaps.h"
+
+// If 1, uses uniform blocks, otherwise fix bone count to 82
+#define USE_UNIFORM_BLOCK_FOR_BONES 0
+
+// 1 to use glVertexAttribIPointer for bone indices, 0 to convert to floats
+#define USE_INT_ATTRIBS 1
+
+//! Attribute array indices.
+enum { TFATTRLOC_POS = 0, TFATTRLOC_NORM = 1, TFATTRLOC_TAN = 2, TFATTRLOC_BONEIDX=3, TFATTRLOC_BONEWEIGHT = 4, TFATTRLOC_SIZE = 5 };
+
+// Shader programs
+enum { TFSHADER_POS = 0, TFSHADER_POSNORM = 1, TFSHADER_POSNORMTAN = 2, TFSHADER_SIZE = 3 };
+
+struct TFShader
+{
+ TFShader() : program(0), vertShader(0), bonesLocation(0) {}
+
+ // Not a dtor, we're storing them in a map, so delete manually in CleanupTransformFeedbackShaders
+ void Release()
+ {
+ if(program)
+ glDeleteProgram(program);
+ if(vertShader)
+ glDeleteShader(vertShader);
+ }
+
+ GLuint program;
+ GLuint vertShader;
+ GLint bonesLocation;
+ GLint attribLocations[TFATTRLOC_SIZE];
+};
+
+// Swap specialization for TFShader
+namespace std
+{
+ template<> void swap(TFShader &a, TFShader &b)
+ {
+ swap(a.program, b.program);
+ swap(a.vertShader, b.vertShader);
+ swap(a.bonesLocation, b.bonesLocation);
+ swap(a.attribLocations, b.attribLocations);
+ }
+}
+
+// Map to store shaders. the key is channelMap + (bonesPerVertex << 16)
+typedef std::map<UInt32, TFShader> TFShaderMap;
+
+static TFShaderMap tfShaders;
+
+const char *tfShaderAttribNames[TFATTRLOC_SIZE] = {"in_vertex", "in_normal", "in_tangent", "in_boneIndices", "in_boneWeights" };
+
+//! Fragment shader, common to all programs.
+static GLuint tfFragShader = 0;
+
+
+#define STRINGIFY(x) #x
+
+#if USE_UNIFORM_BLOCK_FOR_BONES
+ #define MATRIX_DECL "uniform MtxBlock { vec4 bones[max_bone_count*3]; } Matrices; \n"
+ #define BUILD_MATRIX "Matrices.bones[bidx + 0], Matrices.bones[bidx + 1], Matrices.bones[bidx + 2]"
+#else
+ #define MATRIX_DECL "uniform vec4 bones[max_bone_count*3];\n"
+ #define BUILD_MATRIX "bones[bidx + 0], bones[bidx + 1], bones[bidx + 2]"
+#endif
+
+// Macro to build shader source.
+#define BUILD_SHADER_2(bonecount, indecl, outdecl, skincalc, outcalc ) \
+ "#version 300 es\n" \
+ "\n" \
+ "const int max_bone_count = " STRINGIFY(bonecount) ";\n" \
+ "in vec3 in_vertex;\n" \
+ indecl \
+ "out vec3 out_pos;\n" \
+ outdecl \
+ "\n" \
+ MATRIX_DECL \
+ "\n" \
+ "mat4 getMatrix(int idx)\n" \
+ "{\n"\
+ " int bidx = idx*3;\n" \
+ " return mat4(" BUILD_MATRIX ", vec4(0.0, 0.0, 0.0, 1.0));\n" \
+ "}\n"\
+ "void main(void)\n" \
+ "{\n" \
+ " vec4 inpos = vec4(in_vertex.xyz, 1.0);\n" \
+ " mat4 localToWorldMatrix = \n" \
+ skincalc \
+ " out_pos = (inpos * localToWorldMatrix).xyz;\n" \
+ " gl_Position = vec4(out_pos.xyz, 1.0);\n" \
+ outcalc \
+ "}"
+
+#if USE_INT_ATTRIBS
+#define BONEINDEXTYPE1 "int"
+#define BONEINDEXTYPE2 "ivec2"
+#define BONEINDEXTYPE4 "ivec4"
+#else
+#define BONEINDEXTYPE1 "float"
+#define BONEINDEXTYPE2 "vec2"
+#define BONEINDEXTYPE4 "vec4"
+#endif
+
+#if USE_UNIFORM_BLOCK_FOR_BONES
+#define BUILD_SHADER( indecl, outdecl, skincalc, outcalc ) \
+ {\
+ BUILD_SHADER_2(32, indecl, outdecl, skincalc, outcalc), \
+ BUILD_SHADER_2(64, indecl, outdecl, skincalc, outcalc), \
+ BUILD_SHADER_2(128, indecl, outdecl, skincalc, outcalc), \
+ BUILD_SHADER_2(256, indecl, outdecl, skincalc, outcalc), \
+ BUILD_SHADER_2(512, indecl, outdecl, skincalc, outcalc), \
+ BUILD_SHADER_2(1024, indecl, outdecl, skincalc, outcalc) }
+#else
+// Just one bonecount, store it in first element
+#define BUILD_SHADER( indecl, outdecl, skincalc, outcalc ) \
+{\
+ BUILD_SHADER_2(82, indecl, outdecl, skincalc, outcalc), "", "", "", "", ""\
+}
+#endif
+// Shaders for each input type, and for various max bone counts (32, 64, 128, 256, 512 and 1024) and bone-per-vertex counts (1, 2, 4 bones per vertex supported, sparse array so third slot is empty).
+static const char *tfShaderSource[TFSHADER_SIZE][4][6] = {
+ // TFSHADER_POS
+ {
+#define IN_DECL "\n"
+#define OUT_DECL "\n"
+#define OUT_CALC "\n"
+ // 1 bone
+ BUILD_SHADER( "in " BONEINDEXTYPE1 " in_boneIndices;\n" IN_DECL,
+ OUT_DECL,
+ " getMatrix(int(in_boneIndices));\n",
+ OUT_CALC
+ ),
+
+ // 2 bones
+ BUILD_SHADER( "in " BONEINDEXTYPE2 " in_boneIndices;\n in vec2 in_boneWeights;\n" IN_DECL,
+ OUT_DECL,
+ " getMatrix(int(in_boneIndices.x)) * in_boneWeights[0] + \n" \
+ " getMatrix(int(in_boneIndices.y)) * in_boneWeights[1] ;\n ",
+ OUT_CALC
+ ),
+ // 3 bones
+ {"", "", "", "", "", ""},
+
+ // 4 bones
+ BUILD_SHADER( "in " BONEINDEXTYPE4 " in_boneIndices;\n in vec4 in_boneWeights;\n" IN_DECL,
+ OUT_DECL,
+ " getMatrix(int(in_boneIndices.x)) * in_boneWeights[0] + \n" \
+ " getMatrix(int(in_boneIndices.y)) * in_boneWeights[1] + \n" \
+ " getMatrix(int(in_boneIndices.z)) * in_boneWeights[2] + \n" \
+ " getMatrix(int(in_boneIndices.w)) * in_boneWeights[3] ;\n",
+ OUT_CALC
+ )
+ }
+ ,
+ // TFSHADER_POSNORM
+ {
+#undef IN_DECL
+#undef OUT_DECL
+#undef OUT_CALC
+#define IN_DECL "in vec3 in_normal;\n"
+#define OUT_DECL "out vec3 out_normal;\n"
+#define OUT_CALC " out_normal = normalize( (vec4(in_normal.xyz, 0.0) * localToWorldMatrix)).xyz;\n"
+ // 1 bone
+ BUILD_SHADER( "in " BONEINDEXTYPE1 " in_boneIndices;\n" IN_DECL,
+ OUT_DECL,
+ " getMatrix(int(in_boneIndices));\n",
+ OUT_CALC
+ ),
+
+ // 2 bones
+ BUILD_SHADER( "in " BONEINDEXTYPE2 " in_boneIndices;\n in vec2 in_boneWeights;\n" IN_DECL,
+ OUT_DECL,
+ " getMatrix(int(in_boneIndices.x)) * in_boneWeights[0] + \n" \
+ " getMatrix(int(in_boneIndices.y)) * in_boneWeights[1] ;\n ",
+ OUT_CALC
+ ),
+ // 3 bones
+ {"", "", "", "", "", ""},
+
+ // 4 bones
+ BUILD_SHADER( "in " BONEINDEXTYPE4 " in_boneIndices;\n in vec4 in_boneWeights;\n" IN_DECL,
+ OUT_DECL,
+ " getMatrix(int(in_boneIndices.x)) * in_boneWeights[0] + \n" \
+ " getMatrix(int(in_boneIndices.y)) * in_boneWeights[1] + \n" \
+ " getMatrix(int(in_boneIndices.z)) * in_boneWeights[2] + \n" \
+ " getMatrix(int(in_boneIndices.w)) * in_boneWeights[3] ;\n",
+ OUT_CALC
+ )
+ },
+// TFSHADER_POSNORMTAN
+ {
+#undef IN_DECL
+#undef OUT_DECL
+#undef OUT_CALC
+#define IN_DECL "in vec3 in_normal;\n in vec4 in_tangent;\n"
+#define OUT_DECL "out vec3 out_normal;\n out vec4 out_tangent;\n"
+#define OUT_CALC " out_normal = normalize( ( vec4(in_normal.xyz, 0.0) * localToWorldMatrix)).xyz;\n" \
+ " out_tangent = vec4( normalize( ( vec4(in_tangent.xyz, 0.0) * localToWorldMatrix)).xyz, in_tangent.w);\n"
+ // 1 bone
+ BUILD_SHADER( "in " BONEINDEXTYPE1 " in_boneIndices;\n" IN_DECL,
+ OUT_DECL,
+ " getMatrix(int(in_boneIndices));\n",
+ OUT_CALC
+ ),
+
+ // 2 bones
+ BUILD_SHADER( "in " BONEINDEXTYPE2 " in_boneIndices;\n in vec2 in_boneWeights;\n" IN_DECL,
+ OUT_DECL,
+ " getMatrix(int(in_boneIndices.x)) * in_boneWeights[0] + \n" \
+ " getMatrix(int(in_boneIndices.y)) * in_boneWeights[1] ;\n ",
+ OUT_CALC
+ ),
+ // 3 bones
+ {"", "", "", "", "", ""},
+
+ // 4 bones
+ BUILD_SHADER( "in " BONEINDEXTYPE4 " in_boneIndices;\n in vec4 in_boneWeights;\n" IN_DECL,
+ OUT_DECL,
+ " getMatrix(int(in_boneIndices.x)) * in_boneWeights[0] + \n" \
+ " getMatrix(int(in_boneIndices.y)) * in_boneWeights[1] + \n" \
+ " getMatrix(int(in_boneIndices.z)) * in_boneWeights[2] + \n" \
+ " getMatrix(int(in_boneIndices.w)) * in_boneWeights[3] ;\n",
+ OUT_CALC
+ )
+ }
+
+#undef IN_DECL
+#undef OUT_DECL
+#undef OUT_CALC
+
+};
+
+#undef BUILD_SHADER
+#undef BUILD_SHADER_2
+#undef STRINGIFY
+#undef MATRIX_DECL
+#undef BUILD_MATRIX
+
+static const char skinFS[] =
+ "#version 300 es\n"
+ "\n"
+ "precision lowp float;\n"
+ "out vec4 outcol;\n"
+ "void main(void) { outcol = vec4(1.0, 1.0, 1.0, 1.0); }\n";
+
+enum TfSkinShaderChannel
+{
+ kTFC_Position = VERTEX_FORMAT1(Vertex),
+ kTFC_Normal = VERTEX_FORMAT1(Normal),
+ kTFC_Tangent = VERTEX_FORMAT1(Tangent)
+};
+
+static GLuint tfTransformFeedback = 0;
+static GLuint GetTransformFeedbackObject(void)
+{
+ if(!tfTransformFeedback)
+ GLES_CHK(glGenTransformFeedbacks(1, &tfTransformFeedback));
+ return tfTransformFeedback;
+}
+
+// Note: we might not support all formats all the time.
+static bool DoesVertexFormatQualifyForTransformFeedback(UInt32 shaderChannelsMap)
+{
+ // Must have position, and if has tangents, must have normals as well.
+ bool qualify = (shaderChannelsMap & kTFC_Position) != 0;
+ if ((shaderChannelsMap & kTFC_Tangent) != 0)
+ qualify &= (shaderChannelsMap & kTFC_Normal) != 0;
+
+ return qualify;
+
+}
+
+static UInt32 roundUpToNextPowerOf2(UInt32 in)
+{
+ // Round up to nearest power of 2
+ // http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
+ in--;
+ in |= in >> 1;
+ in |= in >> 2;
+ in |= in >> 4;
+ in |= in >> 8;
+ in |= in >> 16;
+ in++;
+ return in;
+}
+// Get the bones bit index based on bone count. Assumes bonecount is power of 2
+static int getBonesBits(UInt32 boneCount)
+{
+ // Calculate ln2
+ // http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogDeBruijn
+
+ static const int MultiplyDeBruijnBitPosition2[32] =
+ {
+ 0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8,
+ 31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9
+ };
+ UInt32 res = MultiplyDeBruijnBitPosition2[(UInt32)(boneCount * 0x077CB531U) >> 27];
+
+ if(res < 5) // Minimum size is 32 (= 0)
+ return 0;
+
+ return res-5; // Adjust so that 32 = 0, 64 = 1 etc.
+}
+
+static void print_long_string(std::string in)
+{
+ int offs = 0;
+ int len = in.length();
+ const int split = 200;
+ do
+ {
+ printf_console(in.substr(offs, split).c_str());
+ offs+=split;
+ } while (offs < len);
+
+}
+
+// maxBonesBits == Max bone count: 0 = 32, 1 = 64, etc until 5 = 1024
+static TFShader * GetTransformFeedbackShaderProgram(UInt32 shaderChannelsMap, UInt32 bonesPerVertex, UInt32 maxBonesBits)
+{
+ // Check if already created
+ TFShaderMap::iterator itr = tfShaders.find(shaderChannelsMap + (bonesPerVertex << 16) + (maxBonesBits << 19));
+ if(itr != tfShaders.end())
+ {
+ return &(itr->second);
+ }
+
+ // There are only 3 different combinations, and they are always in order. We'll just cut the array length at the call site.
+ const char *varyings[] = {"out_pos", "out_normal", "out_tangent"};
+ GLuint varyingCount = 0;
+ int shaderIdx = 0;
+ if(shaderChannelsMap & kTFC_Tangent)
+ {
+ shaderIdx = TFSHADER_POSNORMTAN;
+ varyingCount = 3;
+ }
+ else if(shaderChannelsMap & kTFC_Normal)
+ {
+ shaderIdx = TFSHADER_POSNORM;
+ varyingCount = 2;
+ }
+ else
+ {
+ shaderIdx = TFSHADER_POS;
+ varyingCount = 1;
+ }
+
+ TFShader res;
+
+ GLint status = 0;
+ GLint shaderLen = 0;
+ const char *code;
+ int i;
+ // Create the fragment shader if it doesn't exist already
+ if(tfFragShader == 0)
+ {
+ tfFragShader = glCreateShader(GL_FRAGMENT_SHADER);
+ shaderLen = strlen(skinFS);
+ code = &skinFS[0];
+ GLES_CHK(glShaderSource(tfFragShader, 1, &code, &shaderLen));
+ GLES_CHK(glCompileShader(tfFragShader));
+ glGetShaderiv(tfFragShader, GL_COMPILE_STATUS, &status);
+ if(status != GL_TRUE)
+ {
+ char temp[512] = "";
+ GLint len = 512;
+ glGetShaderInfoLog(tfFragShader, 512, &len, temp );
+
+ printf_console("ERROR: Unable to compile Transform Feedback fragment shader!\n Error log:\n%s", temp);
+ return 0;
+ }
+ }
+ res.program = glCreateProgram();
+ res.vertShader = glCreateShader(GL_VERTEX_SHADER);
+ shaderLen = strlen(tfShaderSource[shaderIdx][bonesPerVertex-1][maxBonesBits]);
+ code = &tfShaderSource[shaderIdx][bonesPerVertex-1][maxBonesBits][0];
+ GLES_CHK(glShaderSource(res.vertShader, 1, &code, &shaderLen));
+ GLES_CHK(glCompileShader(res.vertShader));
+ glGetShaderiv(res.vertShader, GL_COMPILE_STATUS, &status);
+ if(status != GL_TRUE)
+ {
+ char temp[512] = "";
+ GLint len = 512;
+ glGetShaderInfoLog(res.vertShader, 512, &len, temp );
+
+ printf_console("ERROR: Unable to compile Transform Feedback vertex shader!\n Error log:\n%s", temp);
+ print_long_string(code);
+ return 0;
+ }
+ GLES_CHK(glAttachShader(res.program, res.vertShader));
+ GLES_CHK(glAttachShader(res.program, tfFragShader));
+
+ GLES_CHK(glTransformFeedbackVaryings(res.program, varyingCount, varyings, GL_INTERLEAVED_ATTRIBS));
+
+ GLES_CHK(glLinkProgram(res.program));
+
+ glGetProgramiv(res.program, GL_LINK_STATUS, &status);
+ if(status != GL_TRUE)
+ {
+ char temp[512] = "";
+ GLint len = 512;
+ glGetProgramInfoLog(res.program, 512, &len, temp );
+ printf_console("ERROR: Unable to link Transform Feedback shader! Error: \n%s", temp);
+ print_long_string(code);
+ return 0;
+ }
+
+#if USE_UNIFORM_BLOCK_FOR_BONES
+ res.bonesLocation = glGetUniformBlockIndex(res.program, "MtxBlock");
+#else
+ res.bonesLocation = glGetUniformLocation(res.program, "bones");
+#endif
+
+ // Get the attribute locations. Some of these may be missing so clear the glerror afterwards
+ for(i = 0; i < TFATTRLOC_SIZE; i++)
+ {
+ res.attribLocations[i] = glGetAttribLocation(res.program, tfShaderAttribNames[i]);
+ }
+ // Clear gl error
+ glGetError();
+
+ // Insert into map and return
+ return &(tfShaders.insert(std::make_pair(shaderChannelsMap + (bonesPerVertex << 16) + (maxBonesBits << 19), res)).first->second);
+
+}
+
+static void ReleaseShader(std::pair<UInt32, TFShader> it)
+{
+ it.second.Release();
+}
+
+void TransformFeedbackSkinningInfo::CleanupTransformFeedbackShaders(void)
+{
+ std::for_each(tfShaders.begin(), tfShaders.end(), ReleaseShader);
+ tfShaders.clear();
+
+ if(tfTransformFeedback)
+ {
+ glDeleteTransformFeedbacks(1, &tfTransformFeedback);
+ tfTransformFeedback = NULL;
+ }
+ if(tfFragShader)
+ {
+ glDeleteShader(tfFragShader);
+ tfFragShader = 0;
+ }
+}
+
+TransformFeedbackSkinningInfo::~TransformFeedbackSkinningInfo()
+{
+#define DEL_BUFFER(x) if(x != 0) { GLES_CHK(glDeleteBuffers(1, &x)); x = 0; }
+ DEL_BUFFER(m_SourceVBO);
+#undef DEL_BUFFER
+ if(m_MatrixBuffer)
+ m_MatrixBuffer->Release();
+}
+
+//! Get Vertex size in floats
+UInt32 TransformFeedbackSkinningInfo::GetVertexSize()
+{
+ // Vertex data size
+ UInt32 res = (GetStride() / 4);
+ // Add skin info size
+ if(GetBonesPerVertex() == 1)
+ return res + 1; // Index
+ else if(GetBonesPerVertex() == 2)
+ return res + 4; // 2 indices, 2 weights
+ else
+ return res + 8; // 4 indices, 4 weights
+}
+
+bool TransformFeedbackSkinningInfo::EnsureBuffer()
+{
+ bool dirty = false;
+ if(m_SourceVBO == 0)
+ {
+ GLES_CHK(glGenBuffers(1, &m_SourceVBO));
+ dirty = true;
+ }
+ GLsizei size = GetVertexSize() * GetVertexCount() * sizeof(float);
+ if(m_SourceVBOSize < size)
+ {
+ GLES_CHK(glBindBuffer(GL_UNIFORM_BUFFER, m_SourceVBO));
+ GLES_CHK(glBufferData(GL_UNIFORM_BUFFER, size, NULL, GL_STATIC_DRAW));
+ m_SourceVBOSize = size;
+ dirty = true;
+ GLES_CHK(glBindBuffer(GL_UNIFORM_BUFFER, 0));
+ }
+ return dirty;
+}
+
+
+void TransformFeedbackSkinningInfo::UpdateSourceData(const void *vertData, const BoneInfluence *skinData, bool dirty)
+{
+ dirty |= EnsureBuffer();
+
+ if(!dirty)
+ return;
+
+ std::vector<float> vboData;
+ vboData.resize(GetVertexSize() * GetVertexCount());
+
+ float *dest = &vboData[0];
+ float *vertsrc = (float *)vertData;
+ int vertsize = GetStride() / sizeof(float);
+ const BoneInfluence *bonesrc4 = skinData;
+ const BoneInfluence2 *bonesrc2 = (BoneInfluence2 *)skinData;
+ const int *bonesrc1 = (int *)skinData;
+
+ for(int i = 0; i < GetVertexCount(); i++)
+ {
+ std::copy(vertsrc, vertsrc+vertsize, dest);
+ dest += vertsize;
+ vertsrc += vertsize;
+ switch(GetBonesPerVertex())
+ {
+ default:
+ case 1:
+#if USE_INT_ATTRIBS
+ memcpy(dest, bonesrc1, sizeof(int));
+ dest++;
+ bonesrc1++;
+#else
+ *(dest++) = (float) *(bonesrc1++);
+#endif
+ break;
+ case 2:
+ // Copy weights
+ std::copy(&bonesrc2->weight[0], (&bonesrc2->weight[0])+2, dest);
+ dest += 2;
+#if USE_INT_ATTRIBS
+ memcpy(dest, &bonesrc2->boneIndex[0], sizeof(int)*2);
+ dest+= 2;
+#else
+ *(dest++) = (float) bonesrc2->boneIndex[0];
+ *(dest++) = (float) bonesrc2->boneIndex[1];
+#endif
+ bonesrc2++;
+
+ break;
+ case 4:
+ // Copy weights
+ std::copy(&bonesrc4->weight[0], (&bonesrc4->weight[0])+4, dest);
+ dest += 4;
+#if USE_INT_ATTRIBS
+ memcpy(dest, &bonesrc4->boneIndex[0], sizeof(int)*4);
+ dest+= 4;
+#else
+ *(dest++) = (float) bonesrc4->boneIndex[0];
+ *(dest++) = (float) bonesrc4->boneIndex[1];
+ *(dest++) = (float) bonesrc4->boneIndex[2];
+ *(dest++) = (float) bonesrc4->boneIndex[3];
+#endif
+ bonesrc4++;
+
+ break;
+ }
+ }
+ GLES_CHK(glBindBuffer(GL_UNIFORM_BUFFER, m_SourceVBO));
+ GLES_CHK(glBufferSubData(GL_UNIFORM_BUFFER, 0, vboData.size() * sizeof(float), &vboData[0]));
+ GLES_CHK(glBindBuffer(GL_UNIFORM_BUFFER, 0));
+
+}
+
+
+void TransformFeedbackSkinningInfo::UpdateSourceBones( const int boneCount, const Matrix4x4f* cachedPose )
+{
+ int i;
+ int inputSize = boneCount * 4 * 3 * sizeof(float);
+
+#if USE_UNIFORM_BLOCK_FOR_BONES
+ m_BoneCount = roundUpToNextPowerOf2(boneCount);
+#else
+ m_BoneCount = 82;
+#endif
+
+ UInt32 realBufSize = m_BoneCount * 4 * 3 * sizeof(float);
+
+ // This basically shouldn't happen but just in case (should be released in SkinMesh)
+ if(m_MatrixBuffer)
+ {
+ m_MatrixBuffer->Release();
+ }
+
+ float *dest = NULL;
+
+#if USE_UNIFORM_BLOCK_FOR_BONES
+ m_MatrixBuffer = GetBufferManagerGLES30()->AcquireBuffer(realBufSize, GL_DYNAMIC_DRAW);
+
+ if(gGraphicsCaps.gles30.useMapBuffer)
+ {
+ m_MatrixBuffer->RecreateStorage(realBufSize, GL_DYNAMIC_DRAW);
+ dest = (float *)m_MatrixBuffer->Map(0, realBufSize, GL_MAP_WRITE_BIT|GL_MAP_INVALIDATE_BUFFER_BIT);
+ }
+ else
+#endif
+ {
+ m_CachedPose.resize(realBufSize / sizeof(float));
+ dest = &m_CachedPose[0];
+ }
+
+ int realBoneCount = boneCount;
+ if(boneCount > m_BoneCount)
+ realBoneCount = m_BoneCount;
+
+ for(i = 0; i < realBoneCount; i++)
+ {
+ Matrix4x4f mat = cachedPose[i];
+ mat.Transpose();
+ float *src = mat.GetPtr();
+ std::copy(src, src+12, dest);
+ dest+=12;
+ }
+
+#if USE_UNIFORM_BLOCK_FOR_BONES
+ if(gGraphicsCaps.gles30.useMapBuffer)
+ {
+ m_MatrixBuffer->Unmap();
+ }
+ else
+ {
+ m_MatrixBuffer->RecreateWithData(realBufSize, GL_DYNAMIC_DRAW, (void *)&m_CachedPose[0]);
+ }
+ m_MatrixBuffer->RecordUpdate();
+#endif
+}
+
+// In GfxDeviceGLES30.cpp
+void GLSLUseProgramGLES30(UInt32 programID);
+
+void TransformFeedbackSkinningInfo::SkinMesh( bool last )
+{
+
+ static GLuint s_WorkaroundTFBuf = 0;
+
+ // Qualcomm, srsly?
+ if(s_WorkaroundTFBuf == 0)
+ {
+ glGenBuffers(1, &s_WorkaroundTFBuf);
+ glBindBuffer(GL_COPY_WRITE_BUFFER, s_WorkaroundTFBuf);
+ glBufferData(GL_COPY_WRITE_BUFFER, 1024, NULL, GL_DYNAMIC_DRAW);
+ glBindBuffer(GL_COPY_WRITE_BUFFER, 0);
+ }
+
+#if USE_UNIFORM_BLOCK_FOR_BONES
+ TFShader *shd = GetTransformFeedbackShaderProgram(GetChannelMap(), GetBonesPerVertex(), getBonesBits(m_BoneCount));
+#else
+ TFShader *shd = GetTransformFeedbackShaderProgram(GetChannelMap(), GetBonesPerVertex(), 0);
+#endif
+
+ Assert(shd);
+
+ GLuint tf = GetTransformFeedbackObject();
+ GLES3VBO *vbo = static_cast<GLES3VBO *>(GetDestVBO());
+ GLuint glvbo = vbo->GetSkinningTargetVBO();
+
+ GLES_CHK(glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, glvbo));
+
+
+ GLSLUseProgramGLES30(shd->program);
+
+#if USE_UNIFORM_BLOCK_FOR_BONES
+ GLES_CHK(glUniformBlockBinding(shd->program, shd->bonesLocation, 0));
+
+ if(m_MatrixBuffer)
+ GLES_CHK(glBindBufferBase(GL_UNIFORM_BUFFER, 0, m_MatrixBuffer->GetBuffer()));
+#else
+
+ GLES_CHK(glUniform4fv(shd->bonesLocation, m_CachedPose.size() / 4, &m_CachedPose[0]));
+#endif
+
+ GLES_CHK(glBindBuffer(GL_ARRAY_BUFFER, m_SourceVBO));
+ GLuint stride = GetVertexSize() * sizeof(float);
+ GLES_CHK(glVertexAttribPointer(shd->attribLocations[TFATTRLOC_POS], 3, GL_FLOAT, GL_FALSE, stride, 0));
+ GLES_CHK(glEnableVertexAttribArray(shd->attribLocations[TFATTRLOC_POS]));
+
+ GLuint nextoffs = 12;
+
+ if(GetChannelMap() & kTFC_Normal)
+ {
+ GLES_CHK(glVertexAttribPointer(shd->attribLocations[TFATTRLOC_NORM], 3, GL_FLOAT, GL_FALSE, stride, (void *)nextoffs));
+ GLES_CHK(glEnableVertexAttribArray(shd->attribLocations[TFATTRLOC_NORM]));
+ nextoffs += 12;
+ }
+ if(GetChannelMap() & kTFC_Tangent)
+ {
+ GLES_CHK(glVertexAttribPointer(shd->attribLocations[TFATTRLOC_TAN], 4, GL_FLOAT, GL_FALSE, stride, (void *)nextoffs));
+ GLES_CHK(glEnableVertexAttribArray(shd->attribLocations[TFATTRLOC_TAN]));
+ nextoffs += 16;
+ }
+
+ switch(GetBonesPerVertex())
+ {
+ default:
+ case 1:
+#if USE_INT_ATTRIBS
+ GLES_CHK(glVertexAttribIPointer(shd->attribLocations[TFATTRLOC_BONEIDX], 1, GL_INT, stride, (void *)nextoffs));
+#else
+ GLES_CHK(glVertexAttribPointer(shd->attribLocations[TFATTRLOC_BONEIDX], 1, GL_FLOAT, GL_FALSE, stride, (void *)nextoffs));
+#endif
+ GLES_CHK(glEnableVertexAttribArray(shd->attribLocations[TFATTRLOC_BONEIDX]));
+
+ break;
+ case 2:
+ GLES_CHK(glVertexAttribPointer(shd->attribLocations[TFATTRLOC_BONEWEIGHT], 2, GL_FLOAT, GL_FALSE, stride,(void *)nextoffs));
+ GLES_CHK(glEnableVertexAttribArray(shd->attribLocations[TFATTRLOC_BONEWEIGHT]));
+ nextoffs += 8;
+#if USE_INT_ATTRIBS
+ GLES_CHK(glVertexAttribIPointer(shd->attribLocations[TFATTRLOC_BONEIDX], 2, GL_INT, stride, (void *)nextoffs));
+#else
+ GLES_CHK(glVertexAttribPointer(shd->attribLocations[TFATTRLOC_BONEIDX], 2, GL_FLOAT, GL_FALSE, stride, (void *)nextoffs));
+#endif
+ GLES_CHK(glEnableVertexAttribArray(shd->attribLocations[TFATTRLOC_BONEIDX]));
+ break;
+
+ case 4:
+ GLES_CHK(glVertexAttribPointer(shd->attribLocations[TFATTRLOC_BONEWEIGHT], 4, GL_FLOAT, GL_FALSE, stride,(void *)nextoffs));
+ GLES_CHK(glEnableVertexAttribArray(shd->attribLocations[TFATTRLOC_BONEWEIGHT]));
+ nextoffs += 16;
+#if USE_INT_ATTRIBS
+ GLES_CHK(glVertexAttribIPointer(shd->attribLocations[TFATTRLOC_BONEIDX], 4, GL_INT, stride, (void *)nextoffs));
+#else
+ GLES_CHK(glVertexAttribPointer(shd->attribLocations[TFATTRLOC_BONEIDX], 4, GL_FLOAT,GL_FALSE, stride, (void *)nextoffs));
+#endif
+ GLES_CHK(glEnableVertexAttribArray(shd->attribLocations[TFATTRLOC_BONEIDX]));
+
+ break;
+ }
+
+ GLES_CHK(glBeginTransformFeedback(GL_POINTS));
+
+ GLES_CHK(glEnable(GL_RASTERIZER_DISCARD));
+ GLES_CHK(glDrawArrays(GL_POINTS, 0, GetVertexCount()));
+ GLES_CHK(glDisable(GL_RASTERIZER_DISCARD));
+
+ GLES_CHK(glEndTransformFeedback());
+
+
+ GLES_CHK(glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, s_WorkaroundTFBuf));
+ GLES_CHK(glBindBuffer(GL_ARRAY_BUFFER, 0));
+
+#if USE_UNIFORM_BLOCK_FOR_BONES
+
+ GLES_CHK(glBindBufferBase(GL_UNIFORM_BUFFER, 0, 0));
+ if(m_MatrixBuffer)
+ {
+ m_MatrixBuffer->RecordRender();
+ m_MatrixBuffer->Release();
+ m_MatrixBuffer = NULL;
+ }
+#endif
+ InvalidateVertexInputCacheGLES30();
+}
diff --git a/Runtime/GfxDevice/opengles30/TransformFeedbackSkinnedMesh.h b/Runtime/GfxDevice/opengles30/TransformFeedbackSkinnedMesh.h
new file mode 100644
index 0000000..3e0c094
--- /dev/null
+++ b/Runtime/GfxDevice/opengles30/TransformFeedbackSkinnedMesh.h
@@ -0,0 +1,47 @@
+#ifndef __TRANSFORMFEEDBACKSKINNEDMESH_H__
+#define __TRANSFORMFEEDBACKSKINNEDMESH_H__
+
+#include "Runtime/GfxDevice/GPUSkinningInfo.h"
+#include "Runtime/GfxDevice/opengles30/IncludesGLES30.h"
+#include "Runtime/GfxDevice/opengles30/DataBuffersGLES30.h"
+#include <vector>
+
+class GfxDeviceGLES30;
+
+// Transform Feedback mesh skinning data.
+// Source and destination VBO formats must match.
+class TransformFeedbackSkinningInfo : public GPUSkinningInfo
+{
+ friend class GfxDeviceGLES30;
+private:
+ GLuint m_SourceVBO;
+ GLsizei m_SourceVBOSize;
+
+ std::vector<float> m_CachedPose;
+
+ DataBufferGLES30 *m_MatrixBuffer;
+
+ //! Stores the bone count from the previous call to UpdateSourceBones. Used to select the most suitable shader version.
+ int m_BoneCount;
+
+ UInt32 GetVertexSize();
+
+ bool EnsureBuffer();
+
+ TransformFeedbackSkinningInfo() : GPUSkinningInfo(),
+ m_SourceVBO(0), m_SourceVBOSize(0), m_MatrixBuffer(0), m_BoneCount(0)
+ {}
+
+ virtual ~TransformFeedbackSkinningInfo();
+
+ virtual void UpdateSourceData(const void *vertData, const BoneInfluence *skinData, bool dirty);
+ virtual void UpdateSourceBones(const int boneCount, const Matrix4x4f* cachedPose);
+ virtual void SkinMesh(bool last);
+
+public:
+
+ //! Release shaders that have been created for transform feedback use.
+ static void CleanupTransformFeedbackShaders(void);
+};
+
+#endif
diff --git a/Runtime/GfxDevice/opengles30/UnityGLES30Ext.cpp b/Runtime/GfxDevice/opengles30/UnityGLES30Ext.cpp
new file mode 100644
index 0000000..37fcb09
--- /dev/null
+++ b/Runtime/GfxDevice/opengles30/UnityGLES30Ext.cpp
@@ -0,0 +1,14 @@
+#include "UnityPrefix.h"
+#include "IncludesGLES30.h"
+#include "UnityGLES30Ext.h"
+#include "Runtime/GfxDevice/opengles/ExtensionsGLES.h"
+
+void Gles3ExtFunc::InitExtFunc()
+{
+ glPushGroupMarkerEXT = (glPushGroupMarkerEXTFunc)GetGLExtProcAddress("glPushGroupMarkerEXT");
+ glPopGroupMarkerEXT = (glPopGroupMarkerEXTFunc)GetGLExtProcAddress("glPopGroupMarkerEXT");
+
+ glAlphaFuncQCOM = (glAlphaFuncQCOMFunc)GetGLExtProcAddress("glAlphaFuncQCOM");
+}
+
+Gles3ExtFunc gGles3ExtFunc;
diff --git a/Runtime/GfxDevice/opengles30/UnityGLES30Ext.h b/Runtime/GfxDevice/opengles30/UnityGLES30Ext.h
new file mode 100644
index 0000000..0c8bc64
--- /dev/null
+++ b/Runtime/GfxDevice/opengles30/UnityGLES30Ext.h
@@ -0,0 +1,276 @@
+#pragma once
+#include "Configuration/UnityConfigure.h"
+#include "Runtime/GfxDevice/opengles/ExtensionsGLES.h"
+
+// The reason to have this file is that we use plenty of gles extensions,
+// and not all of them are handled in (all) sdk we build with.
+// Still we do guard all the usages with runtime ifs, so there is no need to intro addidtional preprocessor magic
+
+
+// ----------------------------------------------------------------------------
+// Texture Formats
+//
+
+#ifndef GL_BGRA_EXT
+ #define GL_BGRA_EXT 0x80E1
+#endif
+#ifndef GL_ETC1_RGB8_OES
+ #define GL_ETC1_RGB8_OES 0x8D64
+#endif
+#ifndef GL_COMPRESSED_RGB_S3TC_DXT1_EXT
+ #define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0
+#endif
+#ifndef GL_COMPRESSED_RGBA_S3TC_DXT1_EXT
+ #define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1
+#endif
+#ifndef GL_COMPRESSED_RGBA_S3TC_DXT3_EXT
+ #define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2
+#endif
+#ifndef GL_COMPRESSED_RGBA_S3TC_DXT5_EXT
+ #define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3
+#endif
+#ifndef GL_COMPRESSED_SRGB_S3TC_DXT1_NV
+ #define GL_COMPRESSED_SRGB_S3TC_DXT1_NV 0x8C4C
+#endif
+#ifndef GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_NV
+ #define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_NV 0x8C4D
+#endif
+#ifndef GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_NV
+ #define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_NV 0x8C4E
+#endif
+#ifndef GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_NV
+ #define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_NV 0x8C4F
+#endif
+#ifndef GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG
+ #define GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG 0x8C00
+#endif
+#ifndef GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG
+ #define GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG 0x8C01
+#endif
+#ifndef GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG
+ #define GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG 0x8C02
+#endif
+#ifndef GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG
+ #define GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG 0x8C03
+#endif
+#ifndef GL_ATC_RGB_AMD
+ #define GL_ATC_RGB_AMD 0x8C92
+#endif
+#ifndef GL_ATC_RGBA_INTERPOLATED_ALPHA_AMD
+ #define GL_ATC_RGBA_INTERPOLATED_ALPHA_AMD 0x87EE
+#endif
+
+#ifndef GL_HALF_FLOAT_OES
+ #define GL_HALF_FLOAT_OES 0x8D61
+#endif
+#ifndef GL_SRGB_EXT
+ #define GL_SRGB_EXT 0x8C40
+#endif
+#ifndef GL_SRGB_ALPHA_EXT
+ #define GL_SRGB_ALPHA_EXT 0x8C42
+#endif
+
+
+// ----------------------------------------------------------------------------
+// GL_EXT_blend_minmax
+//
+
+#ifndef GL_MIN_EXT
+ #define GL_MIN_EXT 0x8007
+#endif
+
+#ifndef GL_MAX_EXT
+ #define GL_MAX_EXT 0x8008
+#endif
+
+// ----------------------------------------------------------------------------
+// GL_EXT_debug_marker
+//
+
+typedef void (*glPushGroupMarkerEXTFunc)(int len, const char* name);
+typedef void (*glPopGroupMarkerEXTFunc)();
+
+
+// ----------------------------------------------------------------------------
+// GL_EXT_discard_framebuffer
+//
+
+#ifndef GL_COLOR_EXT
+ #define GL_COLOR_EXT 0x1800
+#endif
+#ifndef GL_DEPTH_EXT
+ #define GL_DEPTH_EXT 0x1801
+#endif
+#ifndef GL_STENCIL_EXT
+ #define GL_STENCIL_EXT 0x1802
+#endif
+
+typedef void (*glDiscardFramebufferEXTFunc)(GLenum target, GLsizei numAttachments, const GLenum *attachments);
+
+
+// ----------------------------------------------------------------------------
+// GL_EXT_occlusion_query_boolean
+// Note: while we dont use occlusion queries, all queries ext are based on that one
+//
+
+#ifndef GL_QUERY_RESULT_EXT
+ #define GL_QUERY_RESULT_EXT 0x8866
+#endif
+#ifndef GL_QUERY_RESULT_AVAILABLE_EXT
+ #define GL_QUERY_RESULT_AVAILABLE_EXT 0x8867
+#endif
+
+typedef void (*glGenQueriesEXTFunc)(GLuint n, GLuint *ids);
+typedef void (*glDeleteQueriesEXTFunc)(GLuint n, const GLuint *ids);
+typedef void (*glBeginQueryEXTFunc)(GLuint target, GLuint id);
+typedef void (*glEndQueryEXTFunc)(GLuint target);
+typedef void (*glGetQueryObjectuivEXTFunc)(GLuint id, GLuint pname, GLuint *params);
+
+
+// ----------------------------------------------------------------------------
+// GL_EXT_shadow_samplers
+//
+
+#ifndef GL_TEXTURE_COMPARE_MODE_EXT
+ #define GL_TEXTURE_COMPARE_MODE_EXT 0x884C
+#endif
+#ifndef GL_TEXTURE_COMPARE_FUNC_EXT
+ #define GL_TEXTURE_COMPARE_FUNC_EXT 0x884D
+#endif
+#ifndef GL_COMPARE_REF_TO_TEXTURE_EXT
+ #define GL_COMPARE_REF_TO_TEXTURE_EXT 0x884E
+#endif
+#ifndef GL_SAMPLER_2D_SHADOW_EXT
+ #define GL_SAMPLER_2D_SHADOW_EXT 0x8B62
+#endif
+
+
+// ----------------------------------------------------------------------------
+// GL_EXT_texture_rg
+//
+
+#ifndef GL_RED_EXT
+ #define GL_RED_EXT 0x1903
+#endif
+#ifndef GL_RG_EXT
+ #define GL_RG_EXT 0x8227
+#endif
+
+
+// ----------------------------------------------------------------------------
+// GL_OES_get_program_binary
+//
+
+#ifndef GL_PROGRAM_BINARY_LENGTH_OES
+ #define GL_PROGRAM_BINARY_LENGTH_OES 0x8741
+#endif
+
+#ifndef GL_NUM_PROGRAM_BINARY_FORMATS_OES
+ #define GL_NUM_PROGRAM_BINARY_FORMATS_OES 0x87FE
+#endif
+
+typedef void (*glGetProgramBinaryOESFunc)(GLuint program, GLsizei bufSize, GLsizei* length, GLenum* binaryFormat, GLvoid* binary);
+typedef void (*glProgramBinaryOESFunc)(GLuint program, GLenum binaryFormat, const GLvoid *binary, GLint length);
+
+
+// ----------------------------------------------------------------------------
+// GL_OES_mapbuffer
+//
+
+#ifndef GL_WRITE_ONLY_OES
+ #define GL_WRITE_ONLY_OES 0x88B9
+#endif
+
+typedef void* (*glMapBufferOESFunc)(GLenum target, GLenum access);
+typedef GLboolean (*glUnmapBufferOESFunc)(GLenum target);
+
+
+// ----------------------------------------------------------------------------
+// GL_APPLE_framebuffer_multisample
+//
+
+#ifndef GL_MAX_SAMPLES_APPLE
+ #define GL_MAX_SAMPLES_APPLE 0x8D57
+#endif
+#ifndef GL_READ_FRAMEBUFFER_APPLE
+ #define GL_READ_FRAMEBUFFER_APPLE 0x8CA8
+#endif
+#ifndef GL_DRAW_FRAMEBUFFER_APPLE
+ #define GL_DRAW_FRAMEBUFFER_APPLE 0x8CA9
+#endif
+
+
+typedef void (*glRenderbufferStorageMultisampleAPPLEFunc)(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height);
+typedef void (*glResolveMultisampleFramebufferAPPLEFunc)(void);
+
+
+// ----------------------------------------------------------------------------
+// GL_IMG_multisampled_render_to_texture
+//
+
+#ifndef GL_MAX_SAMPLES_IMG
+ #define GL_MAX_SAMPLES_IMG 0x9135
+#endif
+
+typedef void (*glRenderbufferStorageMultisampleIMGFunc)(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height);
+typedef void (*glFramebufferTexture2DMultisampleIMGFunc)(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLsizei samples);
+
+
+// ----------------------------------------------------------------------------
+// GL_NV_draw_buffers
+//
+
+#ifndef GL_MAX_DRAW_BUFFERS_NV
+ #define GL_MAX_DRAW_BUFFERS_NV 0x8824
+#endif
+#ifndef GL_COLOR_ATTACHMENT0_NV
+ #define GL_COLOR_ATTACHMENT0_NV 0x8CE0
+#endif
+
+typedef void (*glDrawBuffersNVFunc)(int len, const GLenum* bufs);
+
+
+// ----------------------------------------------------------------------------
+// GL_NV_timer_query
+//
+
+#ifndef GL_TIME_ELAPSED_NV
+ #define GL_TIME_ELAPSED_NV 0x88BF
+#endif
+#ifndef GL_TIMESTAMP_NV
+ #define GL_TIMESTAMP_NV 0x8E28
+#endif
+
+typedef unsigned long long int EGLuint64NV;
+
+typedef void (*glQueryCounterNVFunc)(GLuint target, GLuint id);
+typedef void (*glGetQueryObjectui64vNVFunc)(GLuint id, GLuint pname, EGLuint64NV *params);
+
+
+// ----------------------------------------------------------------------------
+// GL_QCOM_alpha_test
+//
+
+#ifndef GL_ALPHA_TEST_QCOM
+ #define GL_ALPHA_TEST_QCOM 0x0BC0
+#endif
+
+typedef void (*glAlphaFuncQCOMFunc)(GLenum func, GLfloat ref);
+
+
+// ----------------------------------------------------------------------------
+// common place to get function pointers
+//
+
+struct
+Gles3ExtFunc
+{
+ glPushGroupMarkerEXTFunc glPushGroupMarkerEXT;
+ glPopGroupMarkerEXTFunc glPopGroupMarkerEXT;
+
+ glAlphaFuncQCOMFunc glAlphaFuncQCOM;
+
+ void InitExtFunc();
+};
+extern Gles3ExtFunc gGles3ExtFunc;
+
diff --git a/Runtime/GfxDevice/opengles30/UtilsGLES30.cpp b/Runtime/GfxDevice/opengles30/UtilsGLES30.cpp
new file mode 100644
index 0000000..2c28c12
--- /dev/null
+++ b/Runtime/GfxDevice/opengles30/UtilsGLES30.cpp
@@ -0,0 +1,181 @@
+#include "UnityPrefix.h"
+#include "UtilsGLES30.h"
+
+#include "IncludesGLES30.h"
+
+UInt32 GetColorFormatGLES30 (RenderTextureFormat format)
+{
+ switch (format)
+ {
+ case kRTFormatARGB32: return GL_RGBA8;
+ case kRTFormatDepth: return GL_NONE;
+ case kRTFormatARGBHalf: return GL_RGBA16F;
+ case kRTFormatShadowMap: return GL_NONE;
+ case kRTFormatRGB565: return GL_RGB565;
+ case kRTFormatARGB4444: return GL_RGBA4;
+ case kRTFormatARGB1555: return GL_RGB5_A1;
+ case kRTFormatDefault: return GL_RGBA8;
+ case kRTFormatA2R10G10B10: return GL_RGB10_A2;
+ case kRTFormatDefaultHDR: return GL_R11F_G11F_B10F;
+ case kRTFormatARGB64: return GL_RGBA8;
+ case kRTFormatARGBFloat: return GL_RGBA32F;
+ case kRTFormatRGFloat: return GL_RG32F;
+ case kRTFormatRGHalf: return GL_RG16F;
+ case kRTFormatRFloat: return GL_R32F;
+ case kRTFormatRHalf: return GL_R16F;
+ case kRTFormatR8: return GL_R8;
+ case kRTFormatARGBInt: return GL_RGBA32I;
+ case kRTFormatRGInt: return GL_RG32I;
+ case kRTFormatRInt: return GL_R32I;
+ default:
+ Assert("Invalid RenderTextureFormat");
+ return GL_RGBA8;
+ }
+}
+
+UInt32 GetDepthOnlyFormatGLES30 (DepthBufferFormat format)
+{
+ switch (format)
+ {
+ case kDepthFormatNone: return GL_NONE;
+ case kDepthFormat16: return GL_DEPTH_COMPONENT16;
+ case kDepthFormat24: return GL_DEPTH_COMPONENT24;
+ default:
+ Assert("Invalid DepthBufferFormat");
+ return GL_NONE;
+ }
+}
+
+UInt32 GetDepthStencilFormatGLES30 (DepthBufferFormat format)
+{
+ switch (format)
+ {
+ case kDepthFormatNone: return GL_STENCIL_INDEX8;
+ case kDepthFormat16: return GL_DEPTH24_STENCIL8;
+ case kDepthFormat24: return GL_DEPTH24_STENCIL8;
+ default:
+ Assert("Invalid DepthBufferFormat");
+ return GL_NONE;
+ }
+}
+
+TransferFormatGLES30 GetTransferFormatGLES30 (UInt32 internalFormat)
+{
+ switch (internalFormat)
+ {
+ case GL_RGBA32F: return TransferFormatGLES30(GL_RGBA, GL_FLOAT );
+ case GL_RGBA32I: return TransferFormatGLES30(GL_RGBA_INTEGER, GL_INT );
+ case GL_RGBA32UI: return TransferFormatGLES30(GL_RGBA_INTEGER, GL_UNSIGNED_INT );
+ case GL_RGBA16F: return TransferFormatGLES30(GL_RGBA, GL_HALF_FLOAT );
+ case GL_RGBA16I: return TransferFormatGLES30(GL_RGBA_INTEGER, GL_SHORT );
+ case GL_RGBA16UI: return TransferFormatGLES30(GL_RGBA_INTEGER, GL_UNSIGNED_SHORT );
+ case GL_RGBA8: return TransferFormatGLES30(GL_RGBA, GL_UNSIGNED_BYTE );
+ case GL_RGBA8I: return TransferFormatGLES30(GL_RGBA_INTEGER, GL_BYTE );
+ case GL_RGBA8UI: return TransferFormatGLES30(GL_RGBA_INTEGER, GL_UNSIGNED_BYTE );
+ case GL_SRGB8_ALPHA8: return TransferFormatGLES30(GL_RGBA, GL_UNSIGNED_BYTE );
+ case GL_RGB10_A2: return TransferFormatGLES30(GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV );
+ case GL_RGB10_A2UI: return TransferFormatGLES30(GL_RGBA_INTEGER, GL_UNSIGNED_INT_2_10_10_10_REV );
+ case GL_RGBA4: return TransferFormatGLES30(GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4 );
+ case GL_RGB5_A1: return TransferFormatGLES30(GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1 );
+ case GL_RGBA8_SNORM: return TransferFormatGLES30(GL_RGBA, GL_BYTE );
+ case GL_RGB8: return TransferFormatGLES30(GL_RGB, GL_UNSIGNED_BYTE );
+ case GL_RGB565: return TransferFormatGLES30(GL_RGB, GL_UNSIGNED_SHORT_5_6_5 );
+ case GL_R11F_G11F_B10F: return TransferFormatGLES30(GL_RGB, GL_UNSIGNED_INT_10F_11F_11F_REV );
+ case GL_RGB32F: return TransferFormatGLES30(GL_RGB, GL_FLOAT );
+ case GL_RGB32I: return TransferFormatGLES30(GL_RGB_INTEGER, GL_INT );
+ case GL_RGB32UI: return TransferFormatGLES30(GL_RGB_INTEGER, GL_UNSIGNED_INT );
+ case GL_RGB16F: return TransferFormatGLES30(GL_RGB, GL_HALF_FLOAT );
+ case GL_RGB16I: return TransferFormatGLES30(GL_RGB_INTEGER, GL_SHORT );
+ case GL_RGB16UI: return TransferFormatGLES30(GL_RGB_INTEGER, GL_UNSIGNED_SHORT );
+ case GL_RGB8_SNORM: return TransferFormatGLES30(GL_RGB, GL_BYTE );
+ case GL_RGB8I: return TransferFormatGLES30(GL_RGB_INTEGER, GL_BYTE );
+ case GL_RGB8UI: return TransferFormatGLES30(GL_RGB_INTEGER, GL_UNSIGNED_BYTE );
+ case GL_SRGB8: return TransferFormatGLES30(GL_RGB, GL_UNSIGNED_BYTE );
+ case GL_RGB9_E5: return TransferFormatGLES30(GL_RGB, GL_UNSIGNED_INT_5_9_9_9_REV );
+ case GL_RG32F: return TransferFormatGLES30(GL_RG, GL_FLOAT );
+ case GL_RG32I: return TransferFormatGLES30(GL_RG_INTEGER, GL_INT );
+ case GL_RG32UI: return TransferFormatGLES30(GL_RG_INTEGER, GL_UNSIGNED_INT );
+ case GL_RG16F: return TransferFormatGLES30(GL_RG, GL_HALF_FLOAT );
+ case GL_RG16I: return TransferFormatGLES30(GL_RG_INTEGER, GL_SHORT );
+ case GL_RG16UI: return TransferFormatGLES30(GL_RG_INTEGER, GL_UNSIGNED_SHORT );
+ case GL_RG8: return TransferFormatGLES30(GL_RG, GL_UNSIGNED_BYTE );
+ case GL_RG8I: return TransferFormatGLES30(GL_RG_INTEGER, GL_BYTE );
+ case GL_RG8UI: return TransferFormatGLES30(GL_RG_INTEGER, GL_UNSIGNED_BYTE );
+ case GL_RG8_SNORM: return TransferFormatGLES30(GL_RG, GL_BYTE );
+ case GL_R32F: return TransferFormatGLES30(GL_RED, GL_FLOAT );
+ case GL_R32I: return TransferFormatGLES30(GL_RED_INTEGER, GL_INT );
+ case GL_R32UI: return TransferFormatGLES30(GL_RED_INTEGER, GL_UNSIGNED_INT );
+ case GL_R16F: return TransferFormatGLES30(GL_RED, GL_HALF_FLOAT );
+ case GL_R16I: return TransferFormatGLES30(GL_RED_INTEGER, GL_SHORT );
+ case GL_R16UI: return TransferFormatGLES30(GL_RED_INTEGER, GL_UNSIGNED_SHORT );
+ case GL_R8: return TransferFormatGLES30(GL_RED, GL_UNSIGNED_BYTE );
+ case GL_R8I: return TransferFormatGLES30(GL_RED_INTEGER, GL_BYTE );
+ case GL_R8UI: return TransferFormatGLES30(GL_RED_INTEGER, GL_UNSIGNED_BYTE );
+ case GL_R8_SNORM: return TransferFormatGLES30(GL_RED, GL_BYTE );
+ case GL_DEPTH_COMPONENT32F: return TransferFormatGLES30(GL_DEPTH_COMPONENT, GL_FLOAT );
+ case GL_DEPTH_COMPONENT24: return TransferFormatGLES30(GL_DEPTH_COMPONENT, GL_UNSIGNED_INT );
+ case GL_DEPTH_COMPONENT16: return TransferFormatGLES30(GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT );
+ case GL_DEPTH32F_STENCIL8: return TransferFormatGLES30(GL_DEPTH_STENCIL, GL_FLOAT_32_UNSIGNED_INT_24_8_REV );
+ case GL_DEPTH24_STENCIL8: return TransferFormatGLES30(GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8 );
+ default: return TransferFormatGLES30(GL_NONE, GL_NONE );
+ }
+}
+
+UInt32 GetDefaultFramebufferColorFormatGLES30 (void)
+{
+ int redBits = 0;
+ int greenBits = 0;
+ int blueBits = 0;
+ int alphaBits = 0;
+
+ glGetIntegerv(GL_RED_BITS, &redBits);
+ glGetIntegerv(GL_GREEN_BITS, &greenBits);
+ glGetIntegerv(GL_BLUE_BITS, &blueBits);
+ glGetIntegerv(GL_ALPHA_BITS, &alphaBits);
+
+#define PACK_FMT(R, G, B, A) (((R) << 24) | ((G) << 16) | ((B) << 8) | (A))
+
+ // \note [pyry] This may not hold true on some implementations - best effort guess only.
+ switch (PACK_FMT(redBits, greenBits, blueBits, alphaBits))
+ {
+ case PACK_FMT(8,8,8,8): return GL_RGBA8;
+ case PACK_FMT(8,8,8,0): return GL_RGB8;
+ case PACK_FMT(4,4,4,4): return GL_RGBA4;
+ case PACK_FMT(5,5,5,1): return GL_RGB5_A1;
+ case PACK_FMT(5,6,5,0): return GL_RGB565;
+ default: return GL_NONE;
+ }
+
+#undef PACK_FMT
+}
+
+UInt32 GetDefaultFramebufferDepthFormatGLES30 (void)
+{
+ int depthBits = 0;
+ int stencilBits = 0;
+ glGetIntegerv(GL_DEPTH_BITS, &depthBits);
+ glGetIntegerv(GL_STENCIL_BITS, &stencilBits);
+
+ if(stencilBits > 0)
+ {
+ switch (depthBits)
+ {
+ case 32: return GL_DEPTH32F_STENCIL8;
+ case 24: return GL_DEPTH24_STENCIL8;
+ case 16: return GL_DEPTH_COMPONENT16; // There's probably no such config?
+ default: return GL_NONE;
+ }
+
+ }
+ else
+ {
+ switch (depthBits)
+ {
+ case 32: return GL_DEPTH_COMPONENT32F;
+ case 24: return GL_DEPTH_COMPONENT24;
+ case 16: return GL_DEPTH_COMPONENT16;
+ default: return GL_NONE;
+ }
+
+ }
+}
diff --git a/Runtime/GfxDevice/opengles30/UtilsGLES30.h b/Runtime/GfxDevice/opengles30/UtilsGLES30.h
new file mode 100644
index 0000000..cde05de
--- /dev/null
+++ b/Runtime/GfxDevice/opengles30/UtilsGLES30.h
@@ -0,0 +1,37 @@
+#pragma once
+
+#include "UnityPrefix.h"
+#include "Runtime/Graphics/RenderTexture.h"
+
+// Shared GLES3 utilities
+
+class TransferFormatGLES30
+{
+public:
+ UInt32 format;
+ UInt32 dataType;
+
+ TransferFormatGLES30 (UInt32 format_, UInt32 dataType_)
+ : format (format_)
+ , dataType (dataType_)
+ {
+ }
+};
+
+// Map RenderTextureFormat to closest GL sized internal format.
+UInt32 GetColorFormatGLES30 (RenderTextureFormat format);
+
+// Get closest depth internal format.
+UInt32 GetDepthOnlyFormatGLES30 (DepthBufferFormat format);
+
+// Get closest depth&stencil internal format.
+UInt32 GetDepthStencilFormatGLES30 (DepthBufferFormat format);
+
+// Get transfer (upload) format, dataType pair for internal format.
+TransferFormatGLES30 GetTransferFormatGLES30 (UInt32 internalFormat);
+
+// Get default framebuffer (0) internal format (guess based on bits)
+UInt32 GetDefaultFramebufferColorFormatGLES30 (void);
+
+// Get default framebuffer (0) depth format
+UInt32 GetDefaultFramebufferDepthFormatGLES30 (void);
diff --git a/Runtime/GfxDevice/opengles30/VBOGLES30.cpp b/Runtime/GfxDevice/opengles30/VBOGLES30.cpp
new file mode 100644
index 0000000..24d4bba
--- /dev/null
+++ b/Runtime/GfxDevice/opengles30/VBOGLES30.cpp
@@ -0,0 +1,1351 @@
+#include "UnityPrefix.h"
+
+#if GFX_SUPPORTS_OPENGLES30
+#include "VBOGLES30.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+#include "Runtime/Shaders/GraphicsCaps.h"
+#include "Runtime/Misc/Allocator.h"
+#include "Runtime/Filters/Renderer.h"
+#include "Runtime/GfxDevice/ChannelAssigns.h"
+#include "Runtime/Utilities/Prefetch.h"
+#include "Runtime/Math/Matrix4x4.h"
+#include "Runtime/GfxDevice/BatchRendering.h"
+#include "IncludesGLES30.h"
+#include "AssertGLES30.h"
+#include "GpuProgramsGLES30.h"
+#include "DebugGLES30.h"
+#include "Runtime/Profiler/TimeHelper.h"
+#include "Runtime/Utilities/ArrayUtility.h"
+#include "Runtime/GfxDevice/GLESChannels.h"
+#include "Runtime/GfxDevice/GLDataBufferCommon.h"
+#include "Runtime/Profiler/MemoryProfiler.h"
+
+#include <algorithm>
+
+#if 1
+ #define DBG_LOG_VBO_GLES30(...) {}
+#else
+ #define DBG_LOG_VBO_GLES30(...) {printf_console(__VA_ARGS__);printf_console("\n");}
+#endif
+
+enum
+{
+ kDefaultBufferAlign = 64
+};
+
+template <typename T>
+inline T Align (T v, size_t alignment)
+{
+ return (v + (alignment-1)) & ~(alignment-1);
+}
+
+template <typename T>
+inline T AlignToDefault (T v)
+{
+ return Align(v, kDefaultBufferAlign);
+}
+
+// Comparison operators for VertexArrayInfoGLES30 (used in caching)
+
+static inline bool operator< (const VertexInputInfoGLES30& a, const VertexInputInfoGLES30& b)
+{
+ return a.componentType < b.componentType &&
+ a.numComponents < b.numComponents &&
+ a.pointer < b.pointer &&
+ a.stride < b.stride;
+}
+
+static inline bool operator!= (const VertexInputInfoGLES30& a, const VertexInputInfoGLES30& b)
+{
+ return a.componentType != b.componentType ||
+ a.numComponents != b.numComponents ||
+ a.pointer != b.pointer ||
+ a.stride != b.stride;
+}
+
+static inline bool operator== (const VertexInputInfoGLES30& a, const VertexInputInfoGLES30& b) { return !(a != b); }
+
+static bool operator< (const VertexArrayInfoGLES30& a, const VertexArrayInfoGLES30& b)
+{
+ if (a.enabledArrays < b.enabledArrays) return true;
+ else if (b.enabledArrays < a.enabledArrays) return false;
+
+ // Compare buffers first as they are more likely to not match.
+ for (int ndx = 0; ndx < kGLES3MaxVertexAttribs; ndx++)
+ {
+ if (a.enabledArrays & (1<<ndx))
+ {
+ if (a.buffers[ndx] < b.buffers[ndx]) return true;
+ else if (b.buffers[ndx] < a.buffers[ndx]) return false;
+ }
+ }
+
+ for (int ndx = 0; ndx < kGLES3MaxVertexAttribs; ndx++)
+ {
+ if (a.enabledArrays & (1<<ndx))
+ {
+ if (a.arrays[ndx] < b.arrays[ndx]) return true;
+ else if (b.arrays[ndx] < a.arrays[ndx]) return false;
+ }
+ }
+
+ return false; // Equal
+}
+
+static bool operator!= (const VertexArrayInfoGLES30& a, const VertexArrayInfoGLES30& b)
+{
+ if (a.enabledArrays != b.enabledArrays)
+ return true;
+
+ // Compare buffers first as they are more likely to not match.
+ for (int ndx = 0; ndx < kGLES3MaxVertexAttribs; ndx++)
+ {
+ if (a.enabledArrays & (1<<ndx))
+ {
+ if (a.buffers[ndx] != b.buffers[ndx])
+ return true;
+ }
+ }
+
+ for (int ndx = 0; ndx < kGLES3MaxVertexAttribs; ndx++)
+ {
+ if (a.enabledArrays & (1<<ndx))
+ {
+ if (a.arrays[ndx] != b.arrays[ndx])
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static bool operator== (const VertexArrayInfoGLES30& a, const VertexArrayInfoGLES30& b) { return !(a != b); }
+
+struct CompareVertexArrayInfoGLES30
+{
+ bool operator() (const VertexArrayInfoGLES30* a, const VertexArrayInfoGLES30* b) const;
+};
+
+class VertexArrayObjectGLES30
+{
+public:
+ VertexArrayObjectGLES30 (const VertexArrayInfoGLES30& info);
+ ~VertexArrayObjectGLES30 (void);
+
+ UInt32 GetVAO (void) const { return m_vao; }
+ const VertexArrayInfoGLES30* GetInfo (void) const { return &m_info; }
+
+private:
+ VertexArrayObjectGLES30 (const VertexArrayObjectGLES30& other); // Not allowed!
+ VertexArrayObjectGLES30& operator= (const VertexArrayObjectGLES30& other); // Not allowed!
+
+ VertexArrayInfoGLES30 m_info;
+ UInt32 m_vao;
+};
+
+typedef std::map<const VertexArrayInfoGLES30*, VertexArrayObjectGLES30*, CompareVertexArrayInfoGLES30> VertexArrayMapGLES30;
+
+// Utilities
+
+static const GLenum kTopologyGLES3[] =
+{
+ GL_TRIANGLES,
+ GL_TRIANGLE_STRIP,
+ GL_TRIANGLES,
+ GL_LINES,
+ GL_LINE_STRIP,
+ GL_POINTS,
+};
+typedef char kTopologyGLES3SizeAssert[ARRAY_SIZE(kTopologyGLES3) == kPrimitiveTypeCount ? 1 : -1];
+
+static const GLenum kVertexTypeGLES3[] =
+{
+ GL_FLOAT, // kChannelFormatFloat
+ GL_HALF_FLOAT, // kChannelFormatFloat16
+ GL_UNSIGNED_BYTE, // kChannelFormatColor
+ GL_BYTE, // kChannelFormatByte
+};
+typedef char kVertexTypeGLES3Assert[ARRAY_SIZE(kVertexTypeGLES3) == kChannelFormatCount ? 1 : -1];
+
+// Targets by attribute location
+static const VertexComponent kVertexCompTargetsGLES3[] =
+{
+ // \note Indices must match to attribute locations.
+ kVertexCompVertex,
+ kVertexCompColor,
+ kVertexCompNormal,
+ kVertexCompTexCoord0,
+ kVertexCompTexCoord1,
+ kVertexCompTexCoord2,
+ kVertexCompTexCoord3,
+ kVertexCompTexCoord4,
+ kVertexCompTexCoord5,
+ kVertexCompTexCoord6,
+ kVertexCompTexCoord7
+};
+typedef char kVertexCompTargetsGLES3Assert[ARRAY_SIZE(kVertexCompTargetsGLES3) == kGLES3MaxVertexAttribs ? 1 : -1];
+
+// For some reason GfxDevice needs to know whether VBO contains color
+// data in order to set up state properly. So this must be called before
+// doing BeforeDrawCall().
+void VBOContainsColorGLES30 (bool flag); // defined in GfxDeviceGLES30.cpp
+
+static bool IsVertexDataValid (const VertexBufferData& buffer)
+{
+ // Verify streams.
+ {
+ UInt32 shaderChannels = 0;
+ for (int streamNdx = 0; streamNdx < kMaxVertexStreams; streamNdx++)
+ {
+ if ((buffer.streams[streamNdx].channelMask == 0) != (buffer.streams[streamNdx].stride == 0))
+ return false; // No data but enabled channels, or other way around.
+
+ if ((shaderChannels & buffer.streams[streamNdx].channelMask) != 0)
+ return false; // Duplicate channels!
+
+ shaderChannels |= buffer.streams[streamNdx].channelMask;
+ }
+ }
+
+ // Make sure channels point to correct streams.
+ for (int chanNdx = 0; chanNdx < kShaderChannelCount; chanNdx++)
+ {
+ if (buffer.channels[chanNdx].dimension == ChannelInfo::kInvalidDimension)
+ continue;
+
+ int streamNdx = buffer.channels[chanNdx].stream;
+
+ if (streamNdx < 0 || streamNdx >= kMaxVertexStreams)
+ return false;
+
+ if (buffer.streams[streamNdx].channelMask & (1<<chanNdx) == 0)
+ return false; // No such channel in stream.
+ }
+
+ // Verify that streams do not overlap (or otherwise we waste memory).
+ // \todo [pyry] O(n^2), but then again kMaxVertexStreams = 4..
+ for (int streamNdx = 0; streamNdx < kMaxVertexStreams; streamNdx++)
+ {
+ if (buffer.streams[streamNdx].channelMask == 0)
+ continue;
+
+ const int start = buffer.streams[streamNdx].offset;
+ const int end = start + buffer.streams[streamNdx].stride*buffer.vertexCount;
+
+ for (int otherStreamNdx = 0; otherStreamNdx < kMaxVertexStreams; otherStreamNdx++)
+ {
+ if (otherStreamNdx == streamNdx ||
+ buffer.streams[otherStreamNdx].channelMask == 0)
+ continue;
+
+ const int otherStart = buffer.streams[otherStreamNdx].offset;
+ const int otherEnd = otherStart + buffer.streams[otherStreamNdx].stride*buffer.vertexCount;
+
+ if ((start <= otherStart && otherStart < end) || // Start lies inside buffer
+ (start < otherEnd && otherEnd <= end)) // End lies inside buffer
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool IsVertexArrayNormalized (int attribNdx, VertexChannelFormat format)
+{
+ if (attribNdx == 0)
+ return false; // Position is never normalized.
+ else
+ return (format != kChannelFormatFloat && format != kChannelFormatFloat16);
+}
+
+// VAOCacheGLES30
+
+VAOCacheGLES30::VAOCacheGLES30 (void)
+ : m_NextEntryNdx(0)
+{
+}
+
+VAOCacheGLES30::~VAOCacheGLES30 (void)
+{
+ Clear();
+}
+
+void VAOCacheGLES30::Clear (void)
+{
+ for (int ndx = 0; ndx < kCacheSize; ndx++)
+ {
+ delete m_Entries[ndx].vao;
+ m_Entries[ndx].vao = 0;
+ m_Entries[ndx].key = VAOCacheKeyGLES30();
+ }
+
+ m_NextEntryNdx = 0; // Not necessary, but this will place first VAOs in first slots.
+}
+
+inline const VertexArrayObjectGLES30* VAOCacheGLES30::Find (const VAOCacheKeyGLES30& key) const
+{
+ for (int ndx = 0; ndx < kCacheSize; ndx++)
+ {
+ if (m_Entries[ndx].key == key)
+ return m_Entries[ndx].vao;
+ }
+
+ return 0;
+}
+
+void VAOCacheGLES30::Insert (const VAOCacheKeyGLES30& key, VertexArrayObjectGLES30* vao)
+{
+ Assert(!Find(key));
+
+ // Replace last
+ delete m_Entries[m_NextEntryNdx].vao;
+ m_Entries[m_NextEntryNdx].key = key;
+ m_Entries[m_NextEntryNdx].vao = vao;
+ m_NextEntryNdx = (m_NextEntryNdx + 1) % kCacheSize;
+}
+
+bool VAOCacheGLES30::IsFull (void) const
+{
+ return m_Entries[m_NextEntryNdx].vao != 0;
+}
+
+// VertexArrayObjectGLES30
+
+VertexArrayObjectGLES30::VertexArrayObjectGLES30 (const VertexArrayInfoGLES30& info)
+ : m_info(info)
+ , m_vao (0)
+{
+ UInt32 boundBuffer = 0;
+
+ GLES_CHK(glGenVertexArrays(1, (GLuint*)&m_vao));
+ GLES_CHK(glBindVertexArray(m_vao));
+
+ for (int attribNdx = 0; attribNdx < kGLES3MaxVertexAttribs; attribNdx++)
+ {
+ if ((info.enabledArrays & (1<<attribNdx)) == 0)
+ continue;
+
+ const UInt32 buffer = info.buffers[attribNdx];
+ const int numComponents = info.arrays[attribNdx].numComponents;
+ const GLenum compType = kVertexTypeGLES3[info.arrays[attribNdx].componentType];
+ const bool normalized = IsVertexArrayNormalized(attribNdx, (VertexChannelFormat)info.arrays[attribNdx].componentType);
+ const int stride = info.arrays[attribNdx].stride;
+ const void* pointer = info.arrays[attribNdx].pointer;
+
+ if (buffer != boundBuffer)
+ {
+ GLES_CHK(glBindBuffer(GL_ARRAY_BUFFER, buffer));
+ boundBuffer = buffer;
+ }
+
+ GLES_CHK(glEnableVertexAttribArray(attribNdx));
+ GLES_CHK(glVertexAttribPointer(attribNdx, numComponents, compType, normalized ? GL_TRUE : GL_FALSE, stride, pointer));
+ }
+
+ GLES_CHK(glBindVertexArray(0));
+ GLES_CHK(glBindBuffer(GL_ARRAY_BUFFER, 0));
+}
+
+VertexArrayObjectGLES30::~VertexArrayObjectGLES30 (void)
+{
+ glDeleteVertexArrays(1, (const GLuint*)&m_vao);
+}
+
+// GLES3VBO
+
+GLES3VBO::GLES3VBO (void)
+ : m_IndexBuffer(0)
+{
+}
+
+GLES3VBO::~GLES3VBO (void)
+{
+ Cleanup();
+}
+
+static UInt32 MapStreamModeToBufferUsage (VBO::StreamMode mode)
+{
+ switch (mode)
+ {
+ case VBO::kStreamModeNoAccess: return GL_STATIC_DRAW; // ???
+ case VBO::kStreamModeWritePersist: return GL_STATIC_DRAW;
+ case VBO::kStreamModeDynamic: return GL_STREAM_DRAW;
+ default:
+ return GL_STATIC_DRAW;
+ }
+}
+
+inline DataBufferGLES30* GLES3VBO::GetCurrentBuffer (int streamNdx)
+{
+ return m_StreamBuffers[streamNdx].buffers[m_StreamBuffers[streamNdx].curBufferNdx];
+}
+
+void GLES3VBO::UpdateVertexData (const VertexBufferData& buffer)
+{
+ Assert(IsVertexDataValid(buffer));
+
+ bool streamHasData[kMaxVertexStreams];
+ int streamBufSize[kMaxVertexStreams];
+
+ for (int streamNdx = 0; streamNdx < kMaxVertexStreams; streamNdx++)
+ {
+ const bool hasData = (buffer.streams[streamNdx].channelMask != 0);
+ const int size = buffer.streams[streamNdx].stride * buffer.vertexCount;
+
+ Assert(hasData == (size != 0));
+
+ streamHasData[streamNdx] = hasData;
+ streamBufSize[streamNdx] = size;
+ }
+
+ // Discard all existing buffers.
+ for (int streamNdx = 0; streamNdx < kMaxVertexStreams; streamNdx++)
+ {
+ Stream& stream = m_StreamBuffers[streamNdx];
+
+ for (int bufNdx = 0; bufNdx < kBufferSwapChainSize; bufNdx++)
+ {
+ if (stream.buffers[bufNdx])
+ {
+ stream.buffers[bufNdx]->Release();
+ stream.buffers[bufNdx] = 0;
+ }
+ }
+
+ UNITY_FREE(kMemVertexData, stream.cpuBuf);
+ stream.cpuBuf = 0;
+ }
+
+ m_VAOCache.Clear(); // VAO cache must be cleared
+
+ // Allocate buffers and upload data.
+ for (int streamNdx = 0; streamNdx < kMaxVertexStreams; streamNdx++)
+ {
+ if (!streamHasData[streamNdx])
+ continue;
+
+ const UInt32 usage = MapStreamModeToBufferUsage((StreamMode)m_StreamModes[streamNdx]);
+ const int size = streamBufSize[streamNdx];
+ const UInt8* streamData = buffer.buffer + buffer.streams[streamNdx].offset;
+ Stream& dstStream = m_StreamBuffers[streamNdx];
+
+ Assert(!dstStream.buffers[dstStream.curBufferNdx]);
+
+ dstStream.buffers[dstStream.curBufferNdx] = GetBufferManagerGLES30()->AcquireBuffer(size, usage);
+ dstStream.buffers[dstStream.curBufferNdx]->RecreateWithData(size, usage, streamData);
+
+ dstStream.channelMask = buffer.streams[streamNdx].channelMask;
+ dstStream.stride = buffer.streams[streamNdx].stride;
+
+ // \todo [2013-06-19 pyry] Allocate cpuBuf on-demand once it is not required for Recreate()
+ dstStream.cpuBuf = (UInt8*)UNITY_MALLOC(kMemVertexData, size);
+ ::memcpy(dstStream.cpuBuf, streamData, size);
+ }
+
+ m_VertexCount = buffer.vertexCount;
+ memcpy(&m_Channels[0], &buffer.channels[0], sizeof(ChannelInfoArray));
+}
+
+void GLES3VBO::UpdateIndexData (const IndexBufferData& buffer)
+{
+ const int bufferSize = CalculateIndexBufferSize(buffer);
+ const UInt32 bufferUsage = m_IndicesDynamic ? GL_DYNAMIC_DRAW : GL_STATIC_DRAW;
+
+ Assert(bufferSize > 0);
+
+ if (m_IndexBuffer && BufferUpdateCausesStallGLES30(m_IndexBuffer))
+ {
+ m_IndexBuffer->Release();
+ m_IndexBuffer = 0;
+ }
+
+ if (!m_IndexBuffer)
+ m_IndexBuffer = GetBufferManagerGLES30()->AcquireBuffer(bufferSize, bufferUsage);
+
+ m_IndexBuffer->RecreateWithData(bufferSize, bufferUsage, buffer.indices);
+
+ // Take copy for emulating quads.
+ Assert(kVBOIndexSize == sizeof(UInt16));
+ m_Indices.resize(buffer.count);
+ std::copy((const UInt16*)buffer.indices, (const UInt16*)buffer.indices + buffer.count, m_Indices.begin());
+}
+
+bool GLES3VBO::MapVertexStream (VertexStreamData& outData, unsigned stream)
+{
+ Assert(0 <= stream && stream < kMaxVertexStreams);
+ Assert(!m_IsStreamMapped[stream]); // \note Multiple mappings will screw up buffer swap chain.
+ Assert(m_StreamBuffers[stream].buffers[m_StreamBuffers[stream].curBufferNdx]);
+
+ Stream& mapStream = m_StreamBuffers[stream];
+ const int size = m_VertexCount * mapStream.stride;
+ void* mapPtr = 0;
+
+ if (BufferUpdateCausesStallGLES30(mapStream.buffers[mapStream.curBufferNdx]))
+ {
+ // Advance to next slot.
+ mapStream.curBufferNdx = (mapStream.curBufferNdx + 1) % kBufferSwapChainSize;
+
+ if (!mapStream.buffers[mapStream.curBufferNdx])
+ {
+ const UInt32 usage = MapStreamModeToBufferUsage((StreamMode)m_StreamModes[stream]);
+ DataBufferGLES30* mapBuffer = GetBufferManagerGLES30()->AcquireBuffer(size, usage);
+
+ if (mapBuffer->GetSize() < size)
+ mapBuffer->RecreateStorage(size, usage);
+
+ mapStream.buffers[mapStream.curBufferNdx] = mapBuffer;
+ }
+ }
+
+ if (gGraphicsCaps.gles30.useMapBuffer)
+ {
+ // \note Using write-only mapping always as map is not guaranteed to return old contents anyway.
+ mapPtr = mapStream.buffers[mapStream.curBufferNdx]->Map(0, size, GL_MAP_WRITE_BIT|GL_MAP_INVALIDATE_BUFFER_BIT);
+ }
+ else
+ {
+ // \todo [2013-06-19 pyry] Allocate cpuBuf on-demand once it is not required for Recreate()
+ Assert(mapStream.cpuBuf);
+ mapPtr = mapStream.cpuBuf;
+ }
+
+ outData.channelMask = mapStream.channelMask;
+ outData.stride = mapStream.stride;
+ outData.vertexCount = m_VertexCount;
+ outData.buffer = (UInt8*)mapPtr;
+
+ m_IsStreamMapped[stream] = true;
+
+ return true;
+}
+
+void GLES3VBO::UnmapVertexStream (unsigned stream)
+{
+ Assert(0 <= stream && stream < kMaxVertexStreams);
+ Assert(m_IsStreamMapped[stream]);
+
+ if (gGraphicsCaps.gles30.useMapBuffer)
+ {
+ GetCurrentBuffer(stream)->Unmap();
+ }
+ else
+ {
+ const int size = m_StreamBuffers[stream].stride*m_VertexCount;
+ const UInt32 usage = MapStreamModeToBufferUsage((StreamMode)m_StreamModes[stream]);
+
+ // \note Buffer slot was advanced in MapVertexStream()
+ GetCurrentBuffer(stream)->RecreateWithData(size, usage, m_StreamBuffers[stream].cpuBuf);
+ }
+
+ m_IsStreamMapped[stream] = false;
+}
+
+void GLES3VBO::Cleanup (void)
+{
+ if (m_IndexBuffer)
+ {
+ m_IndexBuffer->Release();
+ m_IndexBuffer = 0;
+ }
+
+ for (int streamNdx = 0; streamNdx < kMaxVertexStreams; streamNdx++)
+ {
+ for (int bufNdx = 0; bufNdx < kBufferSwapChainSize; bufNdx++)
+ {
+ if (m_StreamBuffers[streamNdx].buffers[bufNdx])
+ {
+ m_StreamBuffers[streamNdx].buffers[bufNdx]->Release();
+ m_StreamBuffers[streamNdx].buffers[bufNdx] = 0;
+ }
+ }
+
+ UNITY_FREE(kMemVertexData, m_StreamBuffers[streamNdx].cpuBuf);
+ m_StreamBuffers[streamNdx].cpuBuf = 0;
+ }
+
+ m_VAOCache.Clear();
+}
+
+void GLES3VBO::Recreate (void)
+{
+ // \note Called on context loss.
+
+ if (m_IndexBuffer)
+ {
+ const int bufferSize = (int)m_Indices.size() * kVBOIndexSize;
+ const UInt32 bufferUsage = m_IndicesDynamic ? GL_DYNAMIC_DRAW : GL_STATIC_DRAW;
+
+ m_IndexBuffer->Disown();
+ delete m_IndexBuffer;
+
+ m_IndexBuffer = GetBufferManagerGLES30()->AcquireBuffer(bufferSize, bufferUsage);
+ m_IndexBuffer->RecreateWithData(bufferSize, bufferUsage, &m_Indices[0]);
+ }
+
+ for (int streamNdx = 0; streamNdx < kMaxVertexStreams; streamNdx++)
+ {
+ Stream& stream = m_StreamBuffers[streamNdx];
+ const int bufSize = stream.stride*m_VertexCount;
+ const UInt32 usage = MapStreamModeToBufferUsage((StreamMode)m_StreamModes[streamNdx]);
+
+ for (int bufNdx = 0; bufNdx < kBufferSwapChainSize; bufNdx++)
+ {
+ if (stream.buffers[bufNdx])
+ {
+ stream.buffers[bufNdx]->Disown();
+ delete stream.buffers[bufNdx];
+ stream.buffers[bufNdx] = 0;
+ }
+ }
+
+ stream.curBufferNdx = 0;
+
+ if (bufSize > 0)
+ {
+ Assert(stream.cpuBuf);
+ stream.buffers[0] = GetBufferManagerGLES30()->AcquireBuffer(bufSize, usage);
+ stream.buffers[0]->RecreateWithData(bufSize, usage, stream.cpuBuf);
+ }
+ }
+
+ m_VAOCache.Clear();
+}
+
+bool GLES3VBO::IsVertexBufferLost (void) const
+{
+ return false;
+}
+
+bool GLES3VBO::IsUsingSourceVertices (void) const
+{
+ return false;
+}
+
+bool GLES3VBO::IsUsingSourceIndices (void) const
+{
+ return true;
+}
+
+int GLES3VBO::GetRuntimeMemorySize (void) const
+{
+ int totalSize = 0;
+
+ for (int streamNdx = 0; streamNdx < kMaxVertexStreams; streamNdx++)
+ {
+ for (int bufNdx = 0; bufNdx < kBufferSwapChainSize; bufNdx++)
+ {
+ if (m_StreamBuffers[streamNdx].buffers[bufNdx])
+ totalSize += m_StreamBuffers[streamNdx].buffers[bufNdx]->GetSize();
+ }
+ }
+
+ if (m_IndexBuffer)
+ totalSize += m_IndexBuffer->GetSize();
+
+ return totalSize;
+}
+
+void GLES3VBO::ComputeVertexInputState (VertexArrayInfoGLES30& dst, const ChannelAssigns& channelAssigns)
+{
+ UInt32 availableShaderChannels = 0; // Channels that have data in any of streams.
+ UInt32 enabledTargets = channelAssigns.GetTargetMap();
+
+ for (int streamNdx = 0; streamNdx < kMaxVertexStreams; streamNdx++)
+ availableShaderChannels |= m_StreamBuffers[streamNdx].channelMask;
+
+ for (int attribNdx = 0; attribNdx < kGLES3MaxVertexAttribs; attribNdx++)
+ {
+ const VertexComponent target = kVertexCompTargetsGLES3[attribNdx];
+
+ if ((enabledTargets & (1<<target)) == 0)
+ continue; // Not enabled.
+
+ const ShaderChannel sourceChannel = channelAssigns.GetSourceForTarget(target);
+
+ // \todo [pyry] Uh, what? Channel is enabled, but no valid source exists.
+ if (sourceChannel < 0)
+ continue;
+
+ Assert(0 <= sourceChannel && sourceChannel < kShaderChannelCount);
+
+ if ((availableShaderChannels & (1<<sourceChannel)) == 0)
+ continue; // Not available.
+
+ const Stream& stream = m_StreamBuffers[m_Channels[sourceChannel].stream];
+
+ dst.arrays[attribNdx].componentType = m_Channels[sourceChannel].format;
+ dst.arrays[attribNdx].numComponents = m_Channels[sourceChannel].format == kChannelFormatColor ? 4 : m_Channels[sourceChannel].dimension;
+ dst.arrays[attribNdx].pointer = reinterpret_cast<const void*>(m_Channels[sourceChannel].offset);
+ dst.arrays[attribNdx].stride = stream.stride;
+ dst.buffers[attribNdx] = GetCurrentBuffer(m_Channels[sourceChannel].stream)->GetBuffer();
+
+ dst.enabledArrays |= (1<<attribNdx);
+ }
+
+ // Fixed-function texgen stuff.
+ {
+ GfxDevice& device = GetRealGfxDevice();
+ const int texArrayBase = kGLES3AttribLocationTexCoord0;
+ const int maxTexArrays = std::min(gGraphicsCaps.maxTexUnits, kGLES3MaxVertexAttribs-texArrayBase);
+
+ if (device.IsPositionRequiredForTexGen() && (availableShaderChannels & (1 << kShaderChannelVertex)))
+ {
+ const ChannelInfo& posInfo = m_Channels[kShaderChannelVertex];
+ const Stream& posStream = m_StreamBuffers[posInfo.stream];
+
+ for (int texUnit = 0; texUnit < maxTexArrays; ++texUnit)
+ {
+ if (device.IsPositionRequiredForTexGen(texUnit))
+ {
+ const int arrNdx = texArrayBase + texUnit;
+ dst.arrays[arrNdx].componentType = posInfo.format;
+ dst.arrays[arrNdx].numComponents = posInfo.format == kChannelFormatColor ? 4 : posInfo.dimension;
+ dst.arrays[arrNdx].pointer = reinterpret_cast<const void*>(posInfo.offset);
+ dst.arrays[arrNdx].stride = posStream.stride;
+ dst.buffers[arrNdx] = GetCurrentBuffer(posInfo.stream)->GetBuffer();
+
+ dst.enabledArrays |= (1<<arrNdx);
+ }
+ }
+ }
+
+ if (device.IsNormalRequiredForTexGen() && (availableShaderChannels & (1 << kShaderChannelNormal)))
+ {
+ const ChannelInfo& normInfo = m_Channels[kShaderChannelNormal];
+ const Stream& normStream = m_StreamBuffers[normInfo.stream];
+
+ for (int texUnit = 0; texUnit < maxTexArrays; ++texUnit)
+ {
+ if (device.IsNormalRequiredForTexGen(texUnit))
+ {
+ const int arrNdx = texArrayBase + texUnit;
+ dst.arrays[arrNdx].componentType = normInfo.format;
+ dst.arrays[arrNdx].numComponents = normInfo.format == kChannelFormatColor ? 4 : normInfo.dimension;
+ dst.arrays[arrNdx].pointer = reinterpret_cast<const void*>(normInfo.offset);
+ dst.arrays[arrNdx].stride = normStream.stride;
+ dst.buffers[arrNdx] = GetCurrentBuffer(normInfo.stream)->GetBuffer();
+
+ dst.enabledArrays |= (1<<arrNdx);
+ }
+ }
+ }
+ }
+}
+
+void GLES3VBO::MarkBuffersRendered (const ChannelAssigns& channelAssigns)
+{
+ // \todo [pyry] Mark based on channel assignments
+ for (int ndx = 0; ndx < kMaxVertexStreams; ndx++)
+ {
+ DataBufferGLES30* buffer = GetCurrentBuffer(ndx);
+ if (buffer)
+ buffer->RecordRender();
+ }
+}
+
+void GLES3VBO::DrawVBO (const ChannelAssigns& channels,
+ UInt32 firstIndexByte,
+ UInt32 indexCount,
+ GfxPrimitiveType topology,
+ UInt32 firstVertex,
+ UInt32 vertexCount)
+{
+ Assert(0 <= firstVertex && firstVertex+vertexCount <= m_VertexCount);
+
+ if (topology == kPrimitiveQuads)
+ {
+ // Need to emulate - oh well.
+ // \todo [2013-05-24 pyry] Cache this?
+ const int numQuads = indexCount/4;
+ const int emulatedIndexCount = numQuads * 6;
+ const int emulatedBufSize = emulatedIndexCount * sizeof(UInt16);
+ const UInt32 bufUsage = GL_DYNAMIC_DRAW;
+ std::vector<UInt16> quadIndices (emulatedIndexCount);
+ DataBufferGLES30* indexBuffer = GetBufferManagerGLES30()->AcquireBuffer(emulatedBufSize, bufUsage);
+
+ FillIndexBufferForQuads(&quadIndices[0], emulatedBufSize, (const UInt16*)((UInt8*)&m_Indices[0] + firstIndexByte), numQuads);
+ indexBuffer->RecreateWithData(emulatedBufSize, bufUsage, &quadIndices[0]);
+
+ Draw(indexBuffer, channels, kPrimitiveTriangles, emulatedIndexCount, 0, vertexCount);
+
+ indexBuffer->Release();
+ }
+ else
+ Draw(m_IndexBuffer, channels, topology, indexCount, firstIndexByte, vertexCount);
+}
+
+void GLES3VBO::DrawCustomIndexed (const ChannelAssigns& channels,
+ void* indices,
+ UInt32 indexCount,
+ GfxPrimitiveType topology,
+ UInt32 vertexRangeBegin,
+ UInt32 vertexRangeEnd,
+ UInt32 drawVertexCount)
+{
+ Assert(0 <= vertexRangeBegin && vertexRangeBegin <= vertexRangeEnd && vertexRangeEnd <= m_VertexCount);
+
+ // \note Called only in static batching mode which doesn't do quads.
+ Assert(topology != kPrimitiveQuads);
+
+ const int ndxBufSize = indexCount * kVBOIndexSize;
+ const UInt32 ndxBufUsage = GL_DYNAMIC_DRAW;
+ DataBufferGLES30* indexBuffer = GetBufferManagerGLES30()->AcquireBuffer(ndxBufSize, ndxBufUsage);
+
+ indexBuffer->RecreateWithData(ndxBufSize, ndxBufUsage, indices);
+
+ Draw(indexBuffer, channels, topology, indexCount, 0, vertexRangeEnd-vertexRangeBegin);
+
+ indexBuffer->Release();
+}
+
+void GLES3VBO::Draw (DataBufferGLES30* indexBuffer,
+ const ChannelAssigns& channels,
+ GfxPrimitiveType topology,
+ UInt32 indexCount,
+ UInt32 indexOffset,
+ UInt32 vertexCountForStats)
+{
+ const bool useVAO = true; // \todo [pyry] Get from GfxDevice caps
+ const VertexArrayObjectGLES30* vao = useVAO ? TryGetVAO(channels) : 0;
+
+ Assert(topology != kPrimitiveQuads);
+
+ // Setup other render state
+ VBOContainsColorGLES30(channels.GetSourceForTarget (kVertexCompColor) == kShaderChannelColor);
+ GetRealGfxDevice().BeforeDrawCall(false);
+
+ if (vao)
+ {
+ GLES_CHK(glBindVertexArray(vao->GetVAO()));
+ }
+ else
+ {
+ VertexArrayInfoGLES30 vertexState;
+ ComputeVertexInputState(vertexState, channels);
+ SetupDefaultVertexArrayStateGLES30(vertexState);
+ }
+
+ GLES_CHK(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer->GetBuffer()));
+
+ {
+ ABSOLUTE_TIME drawTime = START_TIME;
+ const int primCount = GetPrimitiveCount(indexCount, topology, false);
+
+ GLES_CHK(glDrawElements(kTopologyGLES3[topology], indexCount, GL_UNSIGNED_SHORT, (const void*)indexOffset));
+
+ drawTime = ELAPSED_TIME(drawTime);
+ GetRealGfxDevice().GetFrameStats().AddDrawCall(primCount, vertexCountForStats, drawTime);
+ }
+
+ GLES_CHK(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
+
+ if (vao)
+ {
+ GLES_CHK(glBindVertexArray(0));
+ }
+
+ // Record render event for used buffers
+ MarkBuffersRendered(channels);
+ indexBuffer->RecordRender();
+}
+
+const VertexArrayObjectGLES30* GLES3VBO::TryGetVAO (const ChannelAssigns& channels)
+{
+ // \note [pyry] VAO cache keys don't handle texgen. It is a rare case and we don't want to pay the
+ // cost of extra logic & storage.
+ {
+ GfxDevice& device = GetRealGfxDevice();
+ if (device.IsPositionRequiredForTexGen() || device.IsNormalRequiredForTexGen())
+ return 0;
+ }
+
+ const VAOCacheKeyGLES30 cacheKey(channels, m_StreamBuffers[0].curBufferNdx,
+ m_StreamBuffers[1].curBufferNdx,
+ m_StreamBuffers[2].curBufferNdx,
+ m_StreamBuffers[3].curBufferNdx);
+
+ const VertexArrayObjectGLES30* cachedVAO = m_VAOCache.Find(cacheKey);
+
+ if (cachedVAO)
+ {
+ return cachedVAO;
+ }
+ else if (!m_VAOCache.IsFull())
+ {
+ DBG_LOG_VBO_GLES30("GLES3VBO::GetVAO(): cache miss, creating new VAO");
+
+ // Map channel assigns + current layout to full vertex state
+ VertexArrayInfoGLES30 vertexState;
+ ComputeVertexInputState(vertexState, channels);
+
+ VertexArrayObjectGLES30* vao = new VertexArrayObjectGLES30(vertexState);
+ m_VAOCache.Insert(cacheKey, vao);
+
+ return vao;
+ }
+ else
+ {
+ // If VAO cache gets full, VBO falls back to using default VAO. That
+ // way we avoid constantly creating new objects in extreme cases.
+ return 0;
+ }
+}
+
+UInt32 GLES3VBO::GetSkinningTargetVBO (void)
+{
+ const int skinStreamNdx = 0; // \todo [2013-05-31 pyry] Can this change?
+ Stream& skinStream = m_StreamBuffers[skinStreamNdx];
+
+ Assert(skinStream.buffers[skinStream.curBufferNdx]);
+
+ if (BufferUpdateCausesStallGLES30(skinStream.buffers[skinStream.curBufferNdx]))
+ {
+ // Move to next slot.
+ skinStream.curBufferNdx = (skinStream.curBufferNdx + 1) % kBufferSwapChainSize;
+
+ if (!skinStream.buffers[skinStream.curBufferNdx])
+ {
+ const UInt32 usage = GL_STREAM_DRAW;
+ const int size = m_VertexCount*skinStream.stride;
+ DataBufferGLES30* buffer = GetBufferManagerGLES30()->AcquireBuffer(size, usage);
+
+ if (buffer->GetSize() < size)
+ buffer->RecreateStorage(size, usage);
+
+ skinStream.buffers[skinStream.curBufferNdx] = buffer;
+ }
+ }
+
+ skinStream.buffers[skinStream.curBufferNdx]->RecordUpdate();
+ return skinStream.buffers[skinStream.curBufferNdx]->GetBuffer();
+}
+
+DynamicGLES3VBO::DynamicGLES3VBO (void)
+ : m_CurVertexBuffer (0)
+ , m_CurIndexBuffer (0)
+ , m_CurRenderMode ((RenderMode)0)
+ , m_CurShaderChannelMask (0)
+ , m_CurStride (0)
+ , m_CurVertexCount (0)
+ , m_CurIndexCount (0)
+ , m_QuadArrayIndexBuffer (0)
+{
+}
+
+DynamicGLES3VBO::~DynamicGLES3VBO (void)
+{
+ Cleanup();
+}
+
+static UInt32 GetDynamicChunkStride (UInt32 shaderChannelMask)
+{
+ UInt32 stride = 0;
+ for (int i = 0; i < kShaderChannelCount; ++i)
+ {
+ if (shaderChannelMask & (1<<i))
+ stride += VBO::GetDefaultChannelByteSize(i);
+ }
+ return stride;
+}
+
+bool DynamicGLES3VBO::GetChunk (UInt32 shaderChannelMask, UInt32 maxVertices, UInt32 maxIndices, RenderMode renderMode, void** outVB, void** outIB)
+{
+ Assert(maxVertices < 65536 && maxIndices < 65536*3);
+ Assert(!((renderMode == kDrawQuads) && (VBO::kMaxQuads*4 < maxVertices)));
+ DebugAssert(outVB != NULL && maxVertices > 0);
+ DebugAssert((renderMode == kDrawIndexedQuads && (outIB != NULL && maxIndices > 0)) ||
+ (renderMode == kDrawIndexedPoints && (outIB != NULL && maxIndices > 0)) ||
+ (renderMode == kDrawIndexedLines && (outIB != NULL && maxIndices > 0)) ||
+ (renderMode == kDrawIndexedTriangles && (outIB != NULL && maxIndices > 0)) ||
+ (renderMode == kDrawIndexedTriangleStrip && (outIB != NULL && maxIndices > 0)) ||
+ (renderMode == kDrawTriangleStrip && (outIB == NULL && maxIndices == 0)) ||
+ (renderMode == kDrawQuads && (outIB == NULL && maxIndices == 0)));
+ DebugAssert(!m_CurVertexBuffer && !m_CurIndexBuffer);
+
+ const UInt32 stride = GetDynamicChunkStride(shaderChannelMask);
+ const UInt32 usage = GL_STREAM_DRAW;
+ const UInt32 vertexBufferSize = AlignToDefault(stride*maxVertices);
+ const UInt32 indexBufferSize = AlignToDefault(kVBOIndexSize*maxIndices);
+
+ const bool useMapBuffer = gGraphicsCaps.gles30.useMapBuffer;
+ const bool mapVertexBuffer = useMapBuffer && vertexBufferSize >= kDataBufferThreshold;
+ const bool mapIndexBuffer = useMapBuffer && indexBufferSize > 0 && indexBufferSize >= kDataBufferThreshold;
+
+ DataBufferGLES30* vertexBuffer = mapVertexBuffer ? GetBufferManagerGLES30()->AcquireBuffer(maxVertices*stride, usage) : 0;
+ DataBufferGLES30* indexBuffer = mapIndexBuffer ? GetBufferManagerGLES30()->AcquireBuffer(indexBufferSize, usage) : 0;
+
+ // \todo [2013-05-31 pyry] Grow buffers in reasonable steps (align to 1k?)
+ if (vertexBuffer && vertexBuffer->GetSize() < vertexBufferSize)
+ vertexBuffer->RecreateStorage(vertexBufferSize, usage);
+
+ if (indexBuffer && indexBuffer->GetSize() < indexBufferSize)
+ indexBuffer->RecreateStorage(indexBufferSize, usage);
+
+ if (!mapVertexBuffer && m_CurVertexData.size() < vertexBufferSize)
+ m_CurVertexData.resize(vertexBufferSize);
+
+ if (!mapIndexBuffer && m_CurIndexData.size() < indexBufferSize)
+ m_CurIndexData.resize(indexBufferSize);
+
+ if (vertexBuffer)
+ *outVB = vertexBuffer->Map(0, vertexBufferSize, GL_MAP_WRITE_BIT|GL_MAP_INVALIDATE_BUFFER_BIT|GL_MAP_FLUSH_EXPLICIT_BIT);
+ else if (!mapVertexBuffer && vertexBufferSize > 0)
+ *outVB = &m_CurVertexData[0];
+
+ if (indexBuffer)
+ *outIB = indexBuffer->Map(0, indexBufferSize, GL_MAP_WRITE_BIT|GL_MAP_INVALIDATE_BUFFER_BIT|GL_MAP_FLUSH_EXPLICIT_BIT);
+ else if (!mapIndexBuffer && indexBufferSize > 0)
+ *outIB = &m_CurIndexData[0];
+
+ m_CurVertexBuffer = vertexBuffer;
+ m_CurIndexBuffer = indexBuffer;
+ m_CurRenderMode = renderMode;
+ m_CurShaderChannelMask = shaderChannelMask;
+ m_CurStride = stride;
+
+ return true;
+}
+
+void DynamicGLES3VBO::ReleaseChunk (UInt32 actualVertices, UInt32 actualIndices)
+{
+ Assert(m_CurVertexCount == 0 && m_CurIndexCount == 0);
+
+ if (m_CurVertexBuffer)
+ {
+ m_CurVertexBuffer->FlushMappedRange(0, AlignToDefault(m_CurStride*actualVertices));
+ m_CurVertexBuffer->Unmap();
+ }
+ else if (actualVertices*m_CurStride >= kDataBufferThreshold)
+ {
+ // Migrate to buffer.
+ const int size = AlignToDefault(actualVertices*m_CurStride);
+ const UInt32 usage = GL_STREAM_DRAW;
+
+ m_CurVertexBuffer = GetBufferManagerGLES30()->AcquireBuffer(size, usage);
+ m_CurVertexBuffer->RecreateWithData(size, usage, &m_CurVertexData[0]);
+ }
+
+ if (m_CurIndexBuffer)
+ {
+ m_CurIndexBuffer->FlushMappedRange(0, AlignToDefault(actualIndices*kVBOIndexSize));
+ m_CurIndexBuffer->Unmap();
+ }
+ else if (actualIndices*kVBOIndexSize >= kDataBufferThreshold)
+ {
+ // Migrate to buffer.
+ const int size = AlignToDefault(actualIndices*kVBOIndexSize);
+ const UInt32 usage = GL_STREAM_DRAW;
+
+ m_CurIndexBuffer = GetBufferManagerGLES30()->AcquireBuffer(size, usage);
+ m_CurIndexBuffer->RecreateWithData(size, usage, &m_CurIndexData[0]);
+ }
+
+ m_CurVertexCount = actualVertices;
+ m_CurIndexCount = actualIndices;
+}
+
+DataBufferGLES30* DynamicGLES3VBO::GetQuadArrayIndexBuffer (int vertexCount)
+{
+ const int quadCount = vertexCount/4;
+ const int quadIndexCount = quadCount * 6;
+ const int indexBufferSize = quadIndexCount * sizeof(UInt16);
+ const UInt32 indexBufferUsage = GL_STATIC_DRAW;
+
+ if (!m_QuadArrayIndexBuffer || m_QuadArrayIndexBuffer->GetSize() < indexBufferSize)
+ {
+ // Need to re-specify index buffer since current is too small
+ std::vector<UInt16> quadIndices(quadIndexCount);
+
+ for (int quadNdx = 0; quadNdx < quadCount; ++quadNdx)
+ {
+ const UInt16 srcBaseNdx = quadNdx*4;
+ const int dstBaseNdx = quadNdx*6;
+ quadIndices[dstBaseNdx + 0] = srcBaseNdx + 1;
+ quadIndices[dstBaseNdx + 1] = srcBaseNdx + 2;
+ quadIndices[dstBaseNdx + 2] = srcBaseNdx;
+ quadIndices[dstBaseNdx + 3] = srcBaseNdx + 2;
+ quadIndices[dstBaseNdx + 4] = srcBaseNdx + 3;
+ quadIndices[dstBaseNdx + 5] = srcBaseNdx;
+ }
+
+ if (m_QuadArrayIndexBuffer && BufferUpdateCausesStallGLES30(m_QuadArrayIndexBuffer))
+ {
+ m_QuadArrayIndexBuffer->Release();
+ m_QuadArrayIndexBuffer = 0;
+ }
+
+ if (!m_QuadArrayIndexBuffer)
+ m_QuadArrayIndexBuffer = GetBufferManagerGLES30()->AcquireBuffer(indexBufferSize, indexBufferUsage);
+
+ m_QuadArrayIndexBuffer->RecreateWithData(indexBufferSize, indexBufferUsage, &quadIndices[0]);
+ }
+
+ return m_QuadArrayIndexBuffer;
+}
+
+void DynamicGLES3VBO::DrawChunk (const ChannelAssigns& channels)
+{
+ // Compute input state
+ VertexArrayInfoGLES30 vertexInputState;
+ ComputeVertexInputState(vertexInputState, channels);
+
+ // \todo [2013-05-31 pyry] Do we want to use VAOs here?
+
+ // Setup state
+ VBOContainsColorGLES30(channels.GetSourceForTarget (kVertexCompColor) == kShaderChannelColor);
+ GetRealGfxDevice().BeforeDrawCall(false);
+ SetupDefaultVertexArrayStateGLES30(vertexInputState);
+
+ const void* indexPtr = m_CurIndexBuffer ? 0 : (m_CurIndexData.empty() ? 0 : &m_CurIndexData[0]);
+ int trianglesForStats = 0;
+
+ if (m_CurIndexBuffer)
+ GLES_CHK(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_CurIndexBuffer->GetBuffer()));
+
+ switch (m_CurRenderMode)
+ {
+ case kDrawIndexedQuads: // \todo [2013-06-13 pyry] This enum shouldn't even be here.
+ case kDrawIndexedTriangles:
+ GLES_CHK(glDrawElements(GL_TRIANGLES, m_CurIndexCount, GL_UNSIGNED_SHORT, indexPtr));
+ trianglesForStats = m_CurIndexCount/3;
+ break;
+
+ case kDrawIndexedTriangleStrip:
+ GLES_CHK(glDrawElements(GL_TRIANGLE_STRIP, m_CurIndexCount, GL_UNSIGNED_SHORT, indexPtr));
+ trianglesForStats = std::max<int>(0, m_CurIndexCount-2);
+ break;
+ case kDrawIndexedPoints:
+ GLES_CHK(glDrawElements(GL_POINTS, m_CurIndexCount, GL_UNSIGNED_SHORT, indexPtr));
+ trianglesForStats = m_CurIndexCount*2; // Assuming one quad
+ break;
+
+ case kDrawIndexedLines:
+ GLES_CHK(glDrawElements(GL_LINES, m_CurIndexCount, GL_UNSIGNED_SHORT, indexPtr));
+ trianglesForStats = m_CurIndexCount;
+ break;
+
+ case kDrawTriangleStrip:
+ GLES_CHK(glDrawArrays(GL_TRIANGLE_STRIP, 0, m_CurVertexCount));
+ trianglesForStats = m_CurVertexCount-2;
+ break;
+
+ case kDrawQuads:
+ {
+ // Need to emulate with indices.
+ DataBufferGLES30* quadIndexBuf = GetQuadArrayIndexBuffer(m_CurVertexCount);
+ GLES_CHK(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, quadIndexBuf->GetBuffer()));
+ GLES_CHK(glDrawElements(GL_TRIANGLES, m_CurVertexCount/4 * 6, GL_UNSIGNED_SHORT, 0));
+ GLES_CHK(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
+ trianglesForStats = m_CurVertexCount/2;
+ quadIndexBuf->RecordRender();
+ break;
+ }
+
+ default:
+ Assert(false);
+ }
+
+ if (m_CurIndexBuffer)
+ {
+ GLES_CHK(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
+ m_CurIndexBuffer->RecordRender();
+ }
+
+ GetRealGfxDevice().GetFrameStats().AddDrawCall(trianglesForStats, m_CurVertexCount);
+
+ // Release buffers and reset state
+ Cleanup();
+}
+
+void DynamicGLES3VBO::Recreate (void)
+{
+ Cleanup();
+}
+
+void DynamicGLES3VBO::Cleanup (void)
+{
+ if (m_CurVertexBuffer)
+ {
+ m_CurVertexBuffer->Release();
+ m_CurVertexBuffer = 0;
+ }
+
+ if (m_CurIndexBuffer)
+ {
+ m_CurIndexBuffer->Release();
+ m_CurIndexBuffer = 0;
+ }
+
+ if (m_QuadArrayIndexBuffer)
+ {
+ m_QuadArrayIndexBuffer->Release();
+ m_QuadArrayIndexBuffer = 0;
+ }
+
+ m_CurRenderMode = (RenderMode)0;
+ m_CurShaderChannelMask = 0;
+ m_CurStride = 0;
+ m_CurVertexCount = 0;
+ m_CurIndexCount = 0;
+
+ m_CurVertexData.clear();
+ m_CurIndexData.clear();
+}
+
+void DynamicGLES3VBO::ComputeVertexInputState (VertexArrayInfoGLES30& dst, const ChannelAssigns& channelAssigns)
+{
+ const UInt32 availableShaderChannels = m_CurShaderChannelMask; // Channels that have data in any of streams.
+ const UInt32 enabledTargets = channelAssigns.GetTargetMap();
+ UInt32 channelOffsets[kShaderChannelCount];
+
+ const UInt8* basePointer = m_CurVertexBuffer ? 0 : &m_CurVertexData[0];
+ const UInt32 buffer = m_CurVertexBuffer ? m_CurVertexBuffer->GetBuffer() : 0;
+
+ // Compute offsets per enabled shader channel.
+ {
+ UInt32 curOffset = 0;
+ for (int ndx = 0; ndx < kShaderChannelCount; ndx++)
+ {
+ if (availableShaderChannels & (1<<ndx))
+ {
+ channelOffsets[ndx] = curOffset;
+ curOffset += VBO::GetDefaultChannelByteSize(ndx);
+ }
+ else
+ channelOffsets[ndx] = 0;
+ }
+ }
+
+ for (int attribNdx = 0; attribNdx < kGLES3MaxVertexAttribs; attribNdx++)
+ {
+ const VertexComponent target = kVertexCompTargetsGLES3[attribNdx];
+
+ if ((enabledTargets & (1<<target)) == 0)
+ continue; // Not enabled.
+
+ const ShaderChannel sourceChannel = channelAssigns.GetSourceForTarget(target);
+
+ // \todo [pyry] Uh, what? Channel is enabled, but no valid source exists.
+ if (sourceChannel < 0)
+ continue;
+
+ Assert(0 <= sourceChannel && sourceChannel < kShaderChannelCount);
+
+ if ((availableShaderChannels & (1<<sourceChannel)) == 0)
+ continue; // Not available.
+
+ dst.arrays[attribNdx].componentType = VBO::GetDefaultChannelFormat(sourceChannel);
+ dst.arrays[attribNdx].numComponents = dst.arrays[attribNdx].componentType == kChannelFormatColor ? 4 : VBO::GetDefaultChannelDimension(sourceChannel);
+ dst.arrays[attribNdx].pointer = basePointer + channelOffsets[sourceChannel];
+ dst.arrays[attribNdx].stride = m_CurStride;
+ dst.buffers[attribNdx] = buffer;
+
+ dst.enabledArrays |= (1<<attribNdx);
+ }
+
+ // Fixed-function texgen stuff. \todo [pyry] Are these even used?
+ {
+ GfxDevice& device = GetRealGfxDevice();
+ const int texArrayBase = 3;
+ const int maxTexArrays = std::min(gGraphicsCaps.maxTexUnits, kGLES3MaxVertexAttribs-texArrayBase);
+
+ if (device.IsPositionRequiredForTexGen() && (availableShaderChannels & (1 << kShaderChannelVertex)))
+ {
+ const ShaderChannel srcChannel = kShaderChannelVertex;
+ const int format = VBO::GetDefaultChannelFormat(srcChannel);
+ const int numComps = VBO::GetDefaultChannelDimension(srcChannel);
+ const UInt32 offset = channelOffsets[srcChannel];
+
+ for (int texUnit = 0; texUnit < maxTexArrays; ++texUnit)
+ {
+ if (device.IsPositionRequiredForTexGen(texUnit))
+ {
+ const int arrNdx = texArrayBase + texUnit;
+ dst.arrays[arrNdx].componentType = format;
+ dst.arrays[arrNdx].numComponents = numComps;
+ dst.arrays[arrNdx].pointer = basePointer + offset;
+ dst.arrays[arrNdx].stride = m_CurStride;
+ dst.buffers[arrNdx] = buffer;
+
+ dst.enabledArrays |= (1<<arrNdx);
+ }
+ }
+ }
+
+ if (device.IsNormalRequiredForTexGen() && (availableShaderChannels & (1 << kShaderChannelNormal)))
+ {
+ const ShaderChannel srcChannel = kShaderChannelNormal;
+ const int format = VBO::GetDefaultChannelFormat(srcChannel);
+ const int numComps = VBO::GetDefaultChannelDimension(srcChannel);
+ const UInt32 offset = channelOffsets[srcChannel];
+
+ for (int texUnit = 0; texUnit < maxTexArrays; ++texUnit)
+ {
+ if (device.IsNormalRequiredForTexGen(texUnit))
+ {
+ const int arrNdx = texArrayBase + texUnit;
+ dst.arrays[arrNdx].componentType = format;
+ dst.arrays[arrNdx].numComponents = numComps;
+ dst.arrays[arrNdx].pointer = basePointer + offset;
+ dst.arrays[arrNdx].stride = m_CurStride;
+ dst.buffers[arrNdx] = buffer;
+
+ dst.enabledArrays |= (1<<arrNdx);
+ }
+ }
+ }
+ }
+}
+
+// \todo [2013-05-31 pyry] Better, more generic state cache
+
+// \note In theory we could use whole VertexArrayInfoGLES30 as state cache, but alas
+// bound buffers can be destroyed, recreated and state cache would be oblivious
+// to the fact that binding is now empty.
+static UInt32 sEnabledArrays = 0;
+
+void InvalidateVertexInputCacheGLES30()
+{
+ sEnabledArrays = 0;
+
+ for (int attribNdx = 0; attribNdx < gGraphicsCaps.gles30.maxAttributes; attribNdx++)
+ GLES_CHK(glDisableVertexAttribArray(attribNdx));
+}
+
+void SetupDefaultVertexArrayStateGLES30 (const VertexArrayInfoGLES30& info)
+{
+ UInt32 curBoundBuffer = 0;
+ GLES_CHK(glBindBuffer(GL_ARRAY_BUFFER, 0));
+
+ for (int attribNdx = 0; attribNdx < kGLES3MaxVertexAttribs; attribNdx++)
+ {
+ const UInt32 enableBit = 1<<attribNdx;
+
+ if (info.enabledArrays & enableBit)
+ {
+ if (!(sEnabledArrays & enableBit))
+ GLES_CHK(glEnableVertexAttribArray(attribNdx));
+
+ const UInt32 buffer = info.buffers[attribNdx];
+ const int numComponents = info.arrays[attribNdx].numComponents;
+ const GLenum compType = kVertexTypeGLES3[info.arrays[attribNdx].componentType];
+ const bool normalized = IsVertexArrayNormalized(attribNdx, (VertexChannelFormat)info.arrays[attribNdx].componentType);
+ const int stride = info.arrays[attribNdx].stride;
+ const void* pointer = info.arrays[attribNdx].pointer;
+
+ if (curBoundBuffer != buffer)
+ {
+ GLES_CHK(glBindBuffer(GL_ARRAY_BUFFER, buffer));
+ curBoundBuffer = buffer;
+ }
+
+ GLES_CHK(glVertexAttribPointer(attribNdx, numComponents, compType, normalized ? GL_TRUE : GL_FALSE, stride, pointer));
+ }
+ else if (sEnabledArrays & enableBit)
+ GLES_CHK(glDisableVertexAttribArray(attribNdx));
+ }
+
+ sEnabledArrays = info.enabledArrays;
+}
+
+#endif // GFX_SUPPORTS_OPENGLES30
diff --git a/Runtime/GfxDevice/opengles30/VBOGLES30.h b/Runtime/GfxDevice/opengles30/VBOGLES30.h
new file mode 100644
index 0000000..9e7f99a
--- /dev/null
+++ b/Runtime/GfxDevice/opengles30/VBOGLES30.h
@@ -0,0 +1,262 @@
+#ifndef VBO_GLES30_H
+#define VBO_GLES30_H
+
+#include "Runtime/Shaders/VBO.h"
+#include "Runtime/GfxDevice/ChannelAssigns.h"
+#include "DataBuffersGLES30.h"
+#include "IncludesGLES30.h"
+
+#include <map>
+
+enum AttribLocationGLES30
+{
+ kGLES3AttribLocationPosition = 0,
+ kGLES3AttribLocationColor = 1,
+ kGLES3AttribLocationNormal = 2,
+ kGLES3AttribLocationTexCoord0 = 3,
+ kGLES3AttribLocationTexCoord1 = 4,
+ kGLES3AttribLocationTexCoord2 = 5,
+ kGLES3AttribLocationTexCoord3 = 6,
+ kGLES3AttribLocationTexCoord4 = 7,
+ kGLES3AttribLocationTexCoord5 = 8,
+ kGLES3AttribLocationTexCoord6 = 9,
+ kGLES3AttribLocationTexCoord7 = 10,
+
+ kGLES3MaxVertexAttribs, //!< Although implementations may support more, this limits VertexArrayInfoGLES30 to a reasonable value.
+};
+
+struct VertexInputInfoGLES30
+{
+ const void* pointer; //!< Pointer or offset.
+ UInt8 componentType; //!< Component type - of type VertexChannelFormat.
+ UInt8 numComponents; //!< Number of components.
+ UInt16 stride; //!< Attribute stride.
+
+ // Following parameters come from outside:
+ // normalize: Deduced based on location and type.
+ // buffer: Comes from VertexArrayInfoGLES30
+
+ VertexInputInfoGLES30 (void)
+ : pointer (0)
+ , componentType (0)
+ , numComponents (0)
+ , stride (0)
+ {
+ }
+
+ VertexInputInfoGLES30 (const void* pointer_, VertexChannelFormat componentType_, int numComponents_, UInt32 stride_)
+ : pointer (pointer_)
+ , componentType ((UInt8)componentType_)
+ , numComponents ((UInt8)numComponents_)
+ , stride ((UInt16)stride_)
+ {
+ // Check overflows.
+ Assert((VertexChannelFormat)componentType == componentType_ &&
+ (int)numComponents == numComponents_ &&
+ (UInt32)stride == stride_);
+ }
+};
+
+struct VertexArrayInfoGLES30
+{
+ UInt32 enabledArrays; //!< Bitmask of enabled arrays.
+ UInt32 buffers[kGLES3MaxVertexAttribs];
+ VertexInputInfoGLES30 arrays[kGLES3MaxVertexAttribs];
+
+ VertexArrayInfoGLES30 (void)
+ : enabledArrays(0)
+ {
+ }
+};
+
+// Setup vertex array state when no VAO is bound.
+void SetupDefaultVertexArrayStateGLES30 (const VertexArrayInfoGLES30& info);
+
+// Invalidate default VA input cache. Call this if you mess up with VA bindings or state gets lost otherwise.
+void InvalidateVertexInputCacheGLES30();
+
+struct VertexArrayInfoGLES30;
+class VertexArrayObjectGLES30;
+
+struct VAOCacheKeyGLES30
+{
+ UInt32 bufferIndices; //!< 4 indices with 8 bits each
+ ChannelAssigns channels;
+
+ inline VAOCacheKeyGLES30 (const ChannelAssigns& channels, UInt32 bufNdx0, UInt32 bufNdx1, UInt32 bufNdx2, UInt32 bufNdx3)
+ : bufferIndices ((bufNdx0 << 24) | (bufNdx1 << 16) | (bufNdx2 << 8) | bufNdx3)
+ , channels (channels)
+ {
+ Assert((bufNdx0 & ~0xff) == 0 &&
+ (bufNdx1 & ~0xff) == 0 &&
+ (bufNdx2 & ~0xff) == 0 &&
+ (bufNdx3 & ~0xff) == 0);
+ }
+
+ inline VAOCacheKeyGLES30 (void)
+ : bufferIndices (~0u)
+ , channels ()
+ {
+ }
+
+ inline bool operator== (const VAOCacheKeyGLES30& other) const
+ {
+ return bufferIndices == other.bufferIndices && channels == other.channels;
+ }
+};
+
+// Cache key must be changed if stream count changes.
+typedef char vaoCacheStreamCountAssert[kMaxVertexStreams == 4 ? 1 : -1];
+
+// VAO cache for single VBO. Can not be shared between VBOs. Cache must be cleared
+// if layout or any buffer in VAO is changed.
+// Linear search is used since VAO cache is very small and most static VBOs should find
+// match in first slot(s) anyway.
+class VAOCacheGLES30
+{
+public:
+ VAOCacheGLES30 (void);
+ ~VAOCacheGLES30 (void);
+
+ const VertexArrayObjectGLES30* Find (const VAOCacheKeyGLES30& key) const;
+ void Insert (const VAOCacheKeyGLES30& key, VertexArrayObjectGLES30* vao);
+ bool IsFull (void) const;
+
+ void Clear (void);
+
+public:
+ VAOCacheGLES30 (const VAOCacheGLES30&); // Not allowed!
+ VAOCacheGLES30& operator= (const VAOCacheGLES30&); // Not allowed!
+
+ enum
+ {
+ kCacheSize = 8
+ };
+
+ struct Entry
+ {
+ VAOCacheKeyGLES30 key;
+ VertexArrayObjectGLES30* vao;
+
+ inline Entry (void) : vao(0) {}
+ };
+
+ Entry m_Entries[kCacheSize];
+ int m_NextEntryNdx;
+};
+
+class GLES3VBO : public VBO
+{
+public:
+ GLES3VBO (void);
+ virtual ~GLES3VBO (void);
+
+ virtual void UpdateVertexData (const VertexBufferData& buffer);
+ virtual void UpdateIndexData (const IndexBufferData& buffer);
+
+ virtual bool MapVertexStream (VertexStreamData& outData, unsigned stream);
+ virtual void UnmapVertexStream (unsigned stream);
+
+ virtual void Cleanup (void);
+ virtual void Recreate (void);
+ virtual bool IsVertexBufferLost (void) const;
+
+ virtual bool IsUsingSourceVertices (void) const;
+ virtual bool IsUsingSourceIndices (void) const;
+
+ virtual int GetRuntimeMemorySize (void) const;
+
+ virtual void DrawVBO (const ChannelAssigns& channels, UInt32 firstIndexByte, UInt32 indexCount,
+ GfxPrimitiveType topology, UInt32 firstVertex, UInt32 vertexCount);
+ virtual void DrawCustomIndexed (const ChannelAssigns& channels, void* indices, UInt32 indexCount,
+ GfxPrimitiveType topology, UInt32 vertexRangeBegin, UInt32 vertexRangeEnd, UInt32 drawVertexCount);
+
+ // This will return VBO for skinned (first) stream
+ UInt32 GetSkinningTargetVBO (void);
+
+
+ virtual void MarkBuffersLost() {}
+
+private:
+ void ComputeVertexInputState (VertexArrayInfoGLES30& dst, const ChannelAssigns& channels);
+ void MarkBuffersRendered (const ChannelAssigns& channels);
+
+ DataBufferGLES30* GetCurrentBuffer (int streamNdx);
+
+ const VertexArrayObjectGLES30* TryGetVAO (const ChannelAssigns& channels);
+
+ void Draw (DataBufferGLES30* indexBuffer,
+ const ChannelAssigns& channels,
+ GfxPrimitiveType topology,
+ UInt32 indexCount,
+ UInt32 indexOffset,
+ UInt32 vertexCountForStats);
+
+ enum
+ {
+ kBufferSwapChainSize = kBufferUpdateMinAgeGLES30+1
+ };
+
+ struct Stream
+ {
+ UInt32 channelMask; //!< Shader channels which this stream contains.
+ UInt32 stride;
+ int curBufferNdx; //!< Current buffer in swap chain
+ DataBufferGLES30* buffers[kBufferSwapChainSize];
+ UInt8* cpuBuf; //!< CPU-side copy, used for Recreate() and emulating mapbuffer.
+
+ Stream (void) : channelMask(0), stride(0), curBufferNdx(0), cpuBuf(0) { memset(&buffers[0], 0, sizeof(buffers)); }
+ };
+
+ // Vertex data
+ Stream m_StreamBuffers[kMaxVertexStreams];
+ ChannelInfoArray m_Channels;
+ int m_VertexCount;
+
+ // Index data
+ std::vector<UInt16> m_Indices; //!< Index data. Copy is kept for emulating quad primitive type.
+ DataBufferGLES30* m_IndexBuffer;
+
+ VAOCacheGLES30 m_VAOCache;
+};
+
+class DynamicGLES3VBO : public DynamicVBO
+{
+public:
+ DynamicGLES3VBO (void);
+ ~DynamicGLES3VBO (void);
+
+ virtual bool GetChunk (UInt32 shaderChannelMask, UInt32 maxVertices, UInt32 maxIndices, RenderMode renderMode, void** outVB, void** outIB);
+ virtual void ReleaseChunk (UInt32 actualVertices, UInt32 actualIndices);
+ virtual void DrawChunk (const ChannelAssigns& channels);
+
+ virtual void Recreate (void);
+
+private:
+ void ComputeVertexInputState (VertexArrayInfoGLES30& info, const ChannelAssigns& channels);
+
+ void Cleanup (void);
+
+ DataBufferGLES30* GetQuadArrayIndexBuffer (int vertexCount);
+
+ enum
+ {
+ kDataBufferThreshold = 1024
+ };
+
+ std::vector<UInt8> m_CurVertexData;
+ std::vector<UInt16> m_CurIndexData;
+
+ DataBufferGLES30* m_CurVertexBuffer;
+ DataBufferGLES30* m_CurIndexBuffer;
+
+ RenderMode m_CurRenderMode;
+ UInt32 m_CurShaderChannelMask;
+ UInt32 m_CurStride;
+ UInt32 m_CurVertexCount;
+ UInt32 m_CurIndexCount;
+
+ DataBufferGLES30* m_QuadArrayIndexBuffer; //!< Used for kDrawQuads mode.
+};
+
+#endif