diff options
Diffstat (limited to 'Runtime/GfxDevice/opengles30')
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(¶ms, -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 |