1 /*
2  * Copyright 2011 Google Inc.
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 
9 #include "gl/GrGLInterface.h"
10 #include "GrGLDefines.h"
11 #include "SkTDArray.h"
12 #include "GrGLNoOpInterface.h"
13 #include "SkTLS.h"
14 
15 // TODO: Delete this file after chrome starts using SkNullGLContext.
16 
17 // added to suppress 'no previous prototype' warning and because this code is duplicated in
18 // SkNullGLContext.cpp
19 namespace {
20 
21 class BufferObj {
22 public:
23     SK_DECLARE_INST_COUNT(BufferObj);
24 
BufferObj(GrGLuint id)25     BufferObj(GrGLuint id) : fID(id), fDataPtr(NULL), fSize(0), fMapped(false) {
26     }
~BufferObj()27     ~BufferObj() { SkDELETE_ARRAY(fDataPtr); }
28 
allocate(GrGLsizeiptr size,const GrGLchar * dataPtr)29     void allocate(GrGLsizeiptr size, const GrGLchar* dataPtr) {
30         if (fDataPtr) {
31             SkASSERT(0 != fSize);
32             SkDELETE_ARRAY(fDataPtr);
33         }
34 
35         fSize = size;
36         fDataPtr = SkNEW_ARRAY(char, size);
37     }
38 
id() const39     GrGLuint id() const          { return fID; }
dataPtr()40     GrGLchar* dataPtr()          { return fDataPtr; }
size() const41     GrGLsizeiptr size() const    { return fSize; }
42 
setMapped(bool mapped)43     void setMapped(bool mapped)  { fMapped = mapped; }
mapped() const44     bool mapped() const          { return fMapped; }
45 
46 private:
47     GrGLuint     fID;
48     GrGLchar*    fDataPtr;
49     GrGLsizeiptr fSize;         // size in bytes
50     bool         fMapped;
51 };
52 
53 // This class maintains a sparsely populated array of buffer pointers.
54 class BufferManager {
55 public:
56     SK_DECLARE_INST_COUNT(BufferManager);
57 
BufferManager()58     BufferManager() : fFreeListHead(kFreeListEnd) {}
59 
~BufferManager()60     ~BufferManager() {
61         // NULL out the entries that are really free list links rather than ptrs before deleting.
62         intptr_t curr = fFreeListHead;
63         while (kFreeListEnd != curr) {
64             intptr_t next = reinterpret_cast<intptr_t>(fBuffers[SkToS32(curr)]);
65             fBuffers[SkToS32(curr)] = NULL;
66             curr = next;
67         }
68 
69         fBuffers.deleteAll();
70     }
71 
lookUp(GrGLuint id)72     BufferObj* lookUp(GrGLuint id) {
73         BufferObj* buffer = fBuffers[id];
74         SkASSERT(buffer && buffer->id() == id);
75         return buffer;
76     }
77 
create()78     BufferObj* create() {
79         GrGLuint id;
80         BufferObj* buffer;
81 
82         if (kFreeListEnd == fFreeListHead) {
83             // no free slots - create a new one
84             id = fBuffers.count();
85             buffer = SkNEW_ARGS(BufferObj, (id));
86             *fBuffers.append() = buffer;
87         } else {
88             // grab the head of the free list and advance the head to the next free slot.
89             id = static_cast<GrGLuint>(fFreeListHead);
90             fFreeListHead = reinterpret_cast<intptr_t>(fBuffers[id]);
91 
92             buffer = SkNEW_ARGS(BufferObj, (id));
93             fBuffers[id] = buffer;
94         }
95 
96         return buffer;
97     }
98 
free(BufferObj * buffer)99     void free(BufferObj* buffer) {
100         SkASSERT(fBuffers.count() > 0);
101 
102         GrGLuint id = buffer->id();
103         SkDELETE(buffer);
104 
105         fBuffers[id] = reinterpret_cast<BufferObj*>(fFreeListHead);
106         fFreeListHead = id;
107     }
108 
109 private:
110     static const intptr_t kFreeListEnd = -1;
111     // Index of the first entry of fBuffers in the free list. Free slots in fBuffers are indices to
112     // the next free slot. The last free slot has a value of kFreeListEnd.
113     intptr_t                fFreeListHead;
114     SkTDArray<BufferObj*>   fBuffers;
115 };
116 
117 /**
118  * The global-to-thread state object for the null interface. All null interfaces on the
119  * same thread currently share one of these. This means two null contexts on the same thread
120  * can interfere with each other. It may make sense to more integrate this into SkNullGLContext
121  * and use it's makeCurrent mechanism.
122  */
123 struct ThreadContext {
124 public:
125     SK_DECLARE_INST_COUNT(ThreadContext);
126 
127     BufferManager   fBufferManager;
128     GrGLuint        fCurrArrayBuffer;
129     GrGLuint        fCurrElementArrayBuffer;
130     GrGLuint        fCurrProgramID;
131     GrGLuint        fCurrShaderID;
132 
Get__anon78c37bd90111::ThreadContext133     static ThreadContext* Get() {
134         return reinterpret_cast<ThreadContext*>(SkTLS::Get(Create, Delete));
135     }
136 
ThreadContext__anon78c37bd90111::ThreadContext137     ThreadContext()
138         : fCurrArrayBuffer(0)
139         , fCurrElementArrayBuffer(0)
140         , fCurrProgramID(0)
141         , fCurrShaderID(0) {}
142 
143 private:
Create__anon78c37bd90111::ThreadContext144     static void* Create() { return SkNEW(ThreadContext ); }
Delete__anon78c37bd90111::ThreadContext145     static void Delete(void* context) { SkDELETE(reinterpret_cast<ThreadContext *>(context)); }
146 };
147 
148 // Functions not declared in GrGLBogusInterface.h (not common with the Debug GL interface).
149 
nullGLActiveTexture(GrGLenum texture)150 GrGLvoid GR_GL_FUNCTION_TYPE nullGLActiveTexture(GrGLenum texture) {}
nullGLAttachShader(GrGLuint program,GrGLuint shader)151 GrGLvoid GR_GL_FUNCTION_TYPE nullGLAttachShader(GrGLuint program, GrGLuint shader) {}
nullGLBeginQuery(GrGLenum target,GrGLuint id)152 GrGLvoid GR_GL_FUNCTION_TYPE nullGLBeginQuery(GrGLenum target, GrGLuint id) {}
nullGLBindAttribLocation(GrGLuint program,GrGLuint index,const char * name)153 GrGLvoid GR_GL_FUNCTION_TYPE nullGLBindAttribLocation(GrGLuint program, GrGLuint index, const char* name) {}
nullGLBindTexture(GrGLenum target,GrGLuint texture)154 GrGLvoid GR_GL_FUNCTION_TYPE nullGLBindTexture(GrGLenum target, GrGLuint texture) {}
nullGLBindVertexArray(GrGLuint id)155 GrGLvoid GR_GL_FUNCTION_TYPE nullGLBindVertexArray(GrGLuint id) {}
156 
nullGLGenBuffers(GrGLsizei n,GrGLuint * ids)157 GrGLvoid GR_GL_FUNCTION_TYPE nullGLGenBuffers(GrGLsizei n, GrGLuint* ids) {
158     ThreadContext* ctx = ThreadContext::Get();
159     for (int i = 0; i < n; ++i) {
160         BufferObj* buffer = ctx->fBufferManager.create();
161         ids[i] = buffer->id();
162     }
163 }
164 
nullGLGenerateMipmap(GrGLenum target)165 GrGLvoid GR_GL_FUNCTION_TYPE nullGLGenerateMipmap(GrGLenum target) {}
166 
nullGLBufferData(GrGLenum target,GrGLsizeiptr size,const GrGLvoid * data,GrGLenum usage)167 GrGLvoid GR_GL_FUNCTION_TYPE nullGLBufferData(GrGLenum target,
168                                               GrGLsizeiptr size,
169                                               const GrGLvoid* data,
170                                               GrGLenum usage) {
171     ThreadContext* ctx = ThreadContext::Get();
172     GrGLuint id = 0;
173 
174     switch (target) {
175     case GR_GL_ARRAY_BUFFER:
176         id = ctx->fCurrArrayBuffer;
177         break;
178     case GR_GL_ELEMENT_ARRAY_BUFFER:
179         id = ctx->fCurrElementArrayBuffer;
180         break;
181     default:
182         SkFAIL("Unexpected target to nullGLBufferData");
183         break;
184     }
185 
186     if (id > 0) {
187         BufferObj* buffer = ctx->fBufferManager.lookUp(id);
188         buffer->allocate(size, (const GrGLchar*) data);
189     }
190 }
191 
nullGLPixelStorei(GrGLenum pname,GrGLint param)192 GrGLvoid GR_GL_FUNCTION_TYPE nullGLPixelStorei(GrGLenum pname, GrGLint param) {}
nullGLReadPixels(GrGLint x,GrGLint y,GrGLsizei width,GrGLsizei height,GrGLenum format,GrGLenum type,GrGLvoid * pixels)193 GrGLvoid GR_GL_FUNCTION_TYPE nullGLReadPixels(GrGLint x, GrGLint y, GrGLsizei width, GrGLsizei height, GrGLenum format, GrGLenum type, GrGLvoid* pixels) {}
nullGLUseProgram(GrGLuint program)194 GrGLvoid GR_GL_FUNCTION_TYPE nullGLUseProgram(GrGLuint program) {}
nullGLViewport(GrGLint x,GrGLint y,GrGLsizei width,GrGLsizei height)195 GrGLvoid GR_GL_FUNCTION_TYPE nullGLViewport(GrGLint x, GrGLint y, GrGLsizei width, GrGLsizei height) {}
nullGLBindFramebuffer(GrGLenum target,GrGLuint framebuffer)196 GrGLvoid GR_GL_FUNCTION_TYPE nullGLBindFramebuffer(GrGLenum target, GrGLuint framebuffer) {}
nullGLBindRenderbuffer(GrGLenum target,GrGLuint renderbuffer)197 GrGLvoid GR_GL_FUNCTION_TYPE nullGLBindRenderbuffer(GrGLenum target, GrGLuint renderbuffer) {}
nullGLDeleteFramebuffers(GrGLsizei n,const GrGLuint * framebuffers)198 GrGLvoid GR_GL_FUNCTION_TYPE nullGLDeleteFramebuffers(GrGLsizei n, const GrGLuint *framebuffers) {}
nullGLDeleteRenderbuffers(GrGLsizei n,const GrGLuint * renderbuffers)199 GrGLvoid GR_GL_FUNCTION_TYPE nullGLDeleteRenderbuffers(GrGLsizei n, const GrGLuint *renderbuffers) {}
nullGLFramebufferRenderbuffer(GrGLenum target,GrGLenum attachment,GrGLenum renderbuffertarget,GrGLuint renderbuffer)200 GrGLvoid GR_GL_FUNCTION_TYPE nullGLFramebufferRenderbuffer(GrGLenum target, GrGLenum attachment, GrGLenum renderbuffertarget, GrGLuint renderbuffer) {}
nullGLFramebufferTexture2D(GrGLenum target,GrGLenum attachment,GrGLenum textarget,GrGLuint texture,GrGLint level)201 GrGLvoid GR_GL_FUNCTION_TYPE nullGLFramebufferTexture2D(GrGLenum target, GrGLenum attachment, GrGLenum textarget, GrGLuint texture, GrGLint level) {}
202 
nullGLCreateProgram()203 GrGLuint GR_GL_FUNCTION_TYPE nullGLCreateProgram() {
204     return ++ThreadContext::Get()->fCurrProgramID;
205 }
206 
nullGLCreateShader(GrGLenum type)207 GrGLuint GR_GL_FUNCTION_TYPE nullGLCreateShader(GrGLenum type) {
208     return ++ThreadContext::Get()->fCurrShaderID;
209 }
210 
211 // same delete used for shaders and programs
nullGLDelete(GrGLuint program)212 GrGLvoid GR_GL_FUNCTION_TYPE nullGLDelete(GrGLuint program) {
213 }
214 
nullGLBindBuffer(GrGLenum target,GrGLuint buffer)215 GrGLvoid GR_GL_FUNCTION_TYPE nullGLBindBuffer(GrGLenum target, GrGLuint buffer) {
216     ThreadContext* ctx = ThreadContext::Get();
217     switch (target) {
218     case GR_GL_ARRAY_BUFFER:
219         ctx->fCurrArrayBuffer = buffer;
220         break;
221     case GR_GL_ELEMENT_ARRAY_BUFFER:
222         ctx->fCurrElementArrayBuffer = buffer;
223         break;
224     }
225 }
226 
227 // deleting a bound buffer has the side effect of binding 0
nullGLDeleteBuffers(GrGLsizei n,const GrGLuint * ids)228 GrGLvoid GR_GL_FUNCTION_TYPE nullGLDeleteBuffers(GrGLsizei n, const GrGLuint* ids) {
229     ThreadContext* ctx = ThreadContext::Get();
230     for (int i = 0; i < n; ++i) {
231         if (ids[i] == ctx->fCurrArrayBuffer) {
232             ctx->fCurrArrayBuffer = 0;
233         }
234         if (ids[i] == ctx->fCurrElementArrayBuffer) {
235             ctx->fCurrElementArrayBuffer = 0;
236         }
237 
238         BufferObj* buffer = ctx->fBufferManager.lookUp(ids[i]);
239         ctx->fBufferManager.free(buffer);
240     }
241 }
242 
nullGLMapBufferRange(GrGLenum target,GrGLintptr offset,GrGLsizeiptr length,GrGLbitfield access)243 GrGLvoid* GR_GL_FUNCTION_TYPE nullGLMapBufferRange(GrGLenum target, GrGLintptr offset,
244                                                    GrGLsizeiptr length, GrGLbitfield access) {
245     ThreadContext* ctx = ThreadContext::Get();
246     GrGLuint id = 0;
247     switch (target) {
248         case GR_GL_ARRAY_BUFFER:
249             id = ctx->fCurrArrayBuffer;
250             break;
251         case GR_GL_ELEMENT_ARRAY_BUFFER:
252             id = ctx->fCurrElementArrayBuffer;
253             break;
254     }
255 
256     if (id > 0) {
257         // We just ignore the offset and length here.
258         BufferObj* buffer = ctx->fBufferManager.lookUp(id);
259         SkASSERT(!buffer->mapped());
260         buffer->setMapped(true);
261         return buffer->dataPtr();
262     }
263     return NULL;
264 }
265 
nullGLMapBuffer(GrGLenum target,GrGLenum access)266 GrGLvoid* GR_GL_FUNCTION_TYPE nullGLMapBuffer(GrGLenum target, GrGLenum access) {
267     ThreadContext* ctx = ThreadContext::Get();
268     GrGLuint id = 0;
269     switch (target) {
270         case GR_GL_ARRAY_BUFFER:
271             id = ctx->fCurrArrayBuffer;
272             break;
273         case GR_GL_ELEMENT_ARRAY_BUFFER:
274             id = ctx->fCurrElementArrayBuffer;
275             break;
276     }
277 
278     if (id > 0) {
279         BufferObj* buffer = ctx->fBufferManager.lookUp(id);
280         SkASSERT(!buffer->mapped());
281         buffer->setMapped(true);
282         return buffer->dataPtr();
283     }
284 
285     SkASSERT(false);
286     return NULL;            // no buffer bound to target
287 }
288 
nullGLFlushMappedBufferRange(GrGLenum target,GrGLintptr offset,GrGLsizeiptr length)289 GrGLvoid GR_GL_FUNCTION_TYPE nullGLFlushMappedBufferRange(GrGLenum target,
290                                                           GrGLintptr offset,
291                                                           GrGLsizeiptr length) {}
292 
293 
nullGLUnmapBuffer(GrGLenum target)294 GrGLboolean GR_GL_FUNCTION_TYPE nullGLUnmapBuffer(GrGLenum target) {
295     ThreadContext* ctx = ThreadContext::Get();
296     GrGLuint id = 0;
297     switch (target) {
298     case GR_GL_ARRAY_BUFFER:
299         id = ctx->fCurrArrayBuffer;
300         break;
301     case GR_GL_ELEMENT_ARRAY_BUFFER:
302         id = ctx->fCurrElementArrayBuffer;
303         break;
304     }
305     if (id > 0) {
306         BufferObj* buffer = ctx->fBufferManager.lookUp(id);
307         SkASSERT(buffer->mapped());
308         buffer->setMapped(false);
309         return GR_GL_TRUE;
310     }
311 
312     GrAlwaysAssert(false);
313     return GR_GL_FALSE; // GR_GL_INVALID_OPERATION;
314 }
315 
nullGLGetBufferParameteriv(GrGLenum target,GrGLenum pname,GrGLint * params)316 GrGLvoid GR_GL_FUNCTION_TYPE nullGLGetBufferParameteriv(GrGLenum target, GrGLenum pname, GrGLint* params) {
317     ThreadContext* ctx = ThreadContext::Get();
318     switch (pname) {
319         case GR_GL_BUFFER_MAPPED: {
320             *params = GR_GL_FALSE;
321             GrGLuint id = 0;
322             switch (target) {
323                 case GR_GL_ARRAY_BUFFER:
324                     id = ctx->fCurrArrayBuffer;
325                     break;
326                 case GR_GL_ELEMENT_ARRAY_BUFFER:
327                     id = ctx->fCurrElementArrayBuffer;
328                     break;
329             }
330             if (id > 0) {
331                 BufferObj* buffer = ctx->fBufferManager.lookUp(id);
332                 if (buffer->mapped()) {
333                     *params = GR_GL_TRUE;
334                 }
335             }
336             break; }
337         default:
338             SkFAIL("Unexpected pname to GetBufferParamateriv");
339             break;
340     }
341 };
342 
343 } // end anonymous namespace
344 
GrGLCreateNullInterface()345 const GrGLInterface* GrGLCreateNullInterface() {
346     GrGLInterface* interface = SkNEW(GrGLInterface);
347 
348     interface->fStandard = kGL_GrGLStandard;
349 
350     GrGLInterface::Functions* functions = &interface->fFunctions;
351     functions->fActiveTexture = nullGLActiveTexture;
352     functions->fAttachShader = nullGLAttachShader;
353     functions->fBeginQuery = nullGLBeginQuery;
354     functions->fBindAttribLocation = nullGLBindAttribLocation;
355     functions->fBindBuffer = nullGLBindBuffer;
356     functions->fBindFragDataLocation = noOpGLBindFragDataLocation;
357     functions->fBindTexture = nullGLBindTexture;
358     functions->fBindVertexArray = nullGLBindVertexArray;
359     functions->fBlendColor = noOpGLBlendColor;
360     functions->fBlendEquation = noOpGLBlendEquation;
361     functions->fBlendFunc = noOpGLBlendFunc;
362     functions->fBufferData = nullGLBufferData;
363     functions->fBufferSubData = noOpGLBufferSubData;
364     functions->fClear = noOpGLClear;
365     functions->fClearColor = noOpGLClearColor;
366     functions->fClearStencil = noOpGLClearStencil;
367     functions->fColorMask = noOpGLColorMask;
368     functions->fCompileShader = noOpGLCompileShader;
369     functions->fCompressedTexImage2D = noOpGLCompressedTexImage2D;
370     functions->fCompressedTexSubImage2D = noOpGLCompressedTexSubImage2D;
371     functions->fCopyTexSubImage2D = noOpGLCopyTexSubImage2D;
372     functions->fCreateProgram = nullGLCreateProgram;
373     functions->fCreateShader = nullGLCreateShader;
374     functions->fCullFace = noOpGLCullFace;
375     functions->fDeleteBuffers = nullGLDeleteBuffers;
376     functions->fDeleteProgram = nullGLDelete;
377     functions->fDeleteQueries = noOpGLDeleteIds;
378     functions->fDeleteShader = nullGLDelete;
379     functions->fDeleteTextures = noOpGLDeleteIds;
380     functions->fDeleteVertexArrays = noOpGLDeleteIds;
381     functions->fDepthMask = noOpGLDepthMask;
382     functions->fDisable = noOpGLDisable;
383     functions->fDisableVertexAttribArray = noOpGLDisableVertexAttribArray;
384     functions->fDrawArrays = noOpGLDrawArrays;
385     functions->fDrawBuffer = noOpGLDrawBuffer;
386     functions->fDrawBuffers = noOpGLDrawBuffers;
387     functions->fDrawElements = noOpGLDrawElements;
388     functions->fEnable = noOpGLEnable;
389     functions->fEnableVertexAttribArray = noOpGLEnableVertexAttribArray;
390     functions->fEndQuery = noOpGLEndQuery;
391     functions->fFinish = noOpGLFinish;
392     functions->fFlush = noOpGLFlush;
393     functions->fFlushMappedBufferRange = nullGLFlushMappedBufferRange;
394     functions->fFrontFace = noOpGLFrontFace;
395     functions->fGenBuffers = nullGLGenBuffers;
396     functions->fGenerateMipmap = nullGLGenerateMipmap;
397     functions->fGenQueries = noOpGLGenIds;
398     functions->fGenTextures = noOpGLGenIds;
399     functions->fGenVertexArrays = noOpGLGenIds;
400     functions->fGetBufferParameteriv = nullGLGetBufferParameteriv;
401     functions->fGetError = noOpGLGetError;
402     functions->fGetIntegerv = noOpGLGetIntegerv;
403     functions->fGetQueryObjecti64v = noOpGLGetQueryObjecti64v;
404     functions->fGetQueryObjectiv = noOpGLGetQueryObjectiv;
405     functions->fGetQueryObjectui64v = noOpGLGetQueryObjectui64v;
406     functions->fGetQueryObjectuiv = noOpGLGetQueryObjectuiv;
407     functions->fGetQueryiv = noOpGLGetQueryiv;
408     functions->fGetProgramInfoLog = noOpGLGetInfoLog;
409     functions->fGetProgramiv = noOpGLGetShaderOrProgramiv;
410     functions->fGetShaderInfoLog = noOpGLGetInfoLog;
411     functions->fGetShaderiv = noOpGLGetShaderOrProgramiv;
412     functions->fGetString = noOpGLGetString;
413     functions->fGetStringi = noOpGLGetStringi;
414     functions->fGetTexLevelParameteriv = noOpGLGetTexLevelParameteriv;
415     functions->fGetUniformLocation = noOpGLGetUniformLocation;
416     functions->fInsertEventMarker = noOpGLInsertEventMarker;
417     functions->fLineWidth = noOpGLLineWidth;
418     functions->fLinkProgram = noOpGLLinkProgram;
419     functions->fMapBuffer = nullGLMapBuffer;
420     functions->fMapBufferRange = nullGLMapBufferRange;
421     functions->fPixelStorei = nullGLPixelStorei;
422     functions->fPopGroupMarker = noOpGLPopGroupMarker;
423     functions->fPushGroupMarker = noOpGLPushGroupMarker;
424     functions->fQueryCounter = noOpGLQueryCounter;
425     functions->fReadBuffer = noOpGLReadBuffer;
426     functions->fReadPixels = nullGLReadPixels;
427     functions->fScissor = noOpGLScissor;
428     functions->fShaderSource = noOpGLShaderSource;
429     functions->fStencilFunc = noOpGLStencilFunc;
430     functions->fStencilFuncSeparate = noOpGLStencilFuncSeparate;
431     functions->fStencilMask = noOpGLStencilMask;
432     functions->fStencilMaskSeparate = noOpGLStencilMaskSeparate;
433     functions->fStencilOp = noOpGLStencilOp;
434     functions->fStencilOpSeparate = noOpGLStencilOpSeparate;
435     functions->fTexImage2D = noOpGLTexImage2D;
436     functions->fTexParameteri = noOpGLTexParameteri;
437     functions->fTexParameteriv = noOpGLTexParameteriv;
438     functions->fTexSubImage2D = noOpGLTexSubImage2D;
439     functions->fTexStorage2D = noOpGLTexStorage2D;
440     functions->fDiscardFramebuffer = noOpGLDiscardFramebuffer;
441     functions->fUniform1f = noOpGLUniform1f;
442     functions->fUniform1i = noOpGLUniform1i;
443     functions->fUniform1fv = noOpGLUniform1fv;
444     functions->fUniform1iv = noOpGLUniform1iv;
445     functions->fUniform2f = noOpGLUniform2f;
446     functions->fUniform2i = noOpGLUniform2i;
447     functions->fUniform2fv = noOpGLUniform2fv;
448     functions->fUniform2iv = noOpGLUniform2iv;
449     functions->fUniform3f = noOpGLUniform3f;
450     functions->fUniform3i = noOpGLUniform3i;
451     functions->fUniform3fv = noOpGLUniform3fv;
452     functions->fUniform3iv = noOpGLUniform3iv;
453     functions->fUniform4f = noOpGLUniform4f;
454     functions->fUniform4i = noOpGLUniform4i;
455     functions->fUniform4fv = noOpGLUniform4fv;
456     functions->fUniform4iv = noOpGLUniform4iv;
457     functions->fUniformMatrix2fv = noOpGLUniformMatrix2fv;
458     functions->fUniformMatrix3fv = noOpGLUniformMatrix3fv;
459     functions->fUniformMatrix4fv = noOpGLUniformMatrix4fv;
460     functions->fUnmapBuffer = nullGLUnmapBuffer;
461     functions->fUseProgram = nullGLUseProgram;
462     functions->fVertexAttrib1f = noOpGLVertexAttrib1f;
463     functions->fVertexAttrib2fv = noOpGLVertexAttrib2fv;
464     functions->fVertexAttrib3fv = noOpGLVertexAttrib3fv;
465     functions->fVertexAttrib4fv = noOpGLVertexAttrib4fv;
466     functions->fVertexAttribPointer = noOpGLVertexAttribPointer;
467     functions->fViewport = nullGLViewport;
468     functions->fBindFramebuffer = nullGLBindFramebuffer;
469     functions->fBindRenderbuffer = nullGLBindRenderbuffer;
470     functions->fCheckFramebufferStatus = noOpGLCheckFramebufferStatus;
471     functions->fDeleteFramebuffers = nullGLDeleteFramebuffers;
472     functions->fDeleteRenderbuffers = nullGLDeleteRenderbuffers;
473     functions->fFramebufferRenderbuffer = nullGLFramebufferRenderbuffer;
474     functions->fFramebufferTexture2D = nullGLFramebufferTexture2D;
475     functions->fGenFramebuffers = noOpGLGenIds;
476     functions->fGenRenderbuffers = noOpGLGenIds;
477     functions->fGetFramebufferAttachmentParameteriv = noOpGLGetFramebufferAttachmentParameteriv;
478     functions->fGetRenderbufferParameteriv = noOpGLGetRenderbufferParameteriv;
479     functions->fRenderbufferStorage = noOpGLRenderbufferStorage;
480     functions->fRenderbufferStorageMultisample = noOpGLRenderbufferStorageMultisample;
481     functions->fBlitFramebuffer = noOpGLBlitFramebuffer;
482     functions->fResolveMultisampleFramebuffer = noOpGLResolveMultisampleFramebuffer;
483     functions->fMatrixLoadf = noOpGLMatrixLoadf;
484     functions->fMatrixLoadIdentity = noOpGLMatrixLoadIdentity;
485     functions->fBindFragDataLocationIndexed = noOpGLBindFragDataLocationIndexed;
486 
487     interface->fExtensions.init(kGL_GrGLStandard, functions->fGetString, functions->fGetStringi,
488                                 functions->fGetIntegerv);
489     return interface;
490 }
491