summaryrefslogtreecommitdiff
path: root/Runtime/GfxDevice/opengles30/VBOGLES30.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Runtime/GfxDevice/opengles30/VBOGLES30.cpp')
-rw-r--r--Runtime/GfxDevice/opengles30/VBOGLES30.cpp1351
1 files changed, 1351 insertions, 0 deletions
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