1 module gfx.gl3.queue;
2 
3 package:
4 
5 import gfx.bindings.opengl.gl;
6 import gfx.core.rc : Disposable;
7 import gfx.graal.cmd;
8 import gfx.graal.queue;
9 
10 final class GlQueue : Queue, Disposable
11 {
12     import gfx.gl3 : GlInfo, GlShare;
13     import gfx.graal.device : Device;
14     import gfx.graal.sync : Fence, Semaphore;
15 
16     private GlShare share;
17     private GlInfo info;
18     private Device _device;
19     private GLuint readFbo;
20     private GLuint vao;
21     private GlState state;
22 
23     this(GlShare share, Device device) {
24         this.share = share;
25         this.info = share.info;
26         _device = device;
27         auto gl = share.gl;
28         gl.GenFramebuffers(1, &readFbo);
29         gl.GenVertexArrays(1, &vao);
30         gl.BindVertexArray(vao);
31     }
32 
33     override void dispose() {
34         auto gl = share.gl;
35         gl.DeleteFramebuffers(1, &readFbo);
36         gl.DeleteVertexArrays(1, &vao);
37     }
38 
39     override @property Device device() {
40         return _device;
41     }
42 
43     override void waitIdle() {}
44 
45     override void submit(Submission[] submissions, Fence fence) {
46         auto gl = share.gl;
47         foreach (ref s; submissions) {
48             foreach (cmdBuf; s.cmdBufs) {
49                 auto glCmdBuf = cast(GlCommandBuffer)cmdBuf;
50                 foreach (cmd; glCmdBuf._cmds) {
51                     cmd.execute(this, gl);
52                 }
53                 if (!glCmdBuf._persistent) {
54                     glCmdBuf._cmds.length = 0;
55                 }
56             }
57         }
58     }
59 
60     override void present(Semaphore[] waitSems, PresentRequest[] prs) {
61         import gfx.gl3.resource : GlImage, GlImgType;
62         import gfx.gl3.swapchain : GlSurface, GlSwapchain;
63         auto gl = share.gl;
64 
65         foreach (i, pr; prs) {
66             auto sc = cast(GlSwapchain)pr.swapChain;
67             auto surf = sc.surface;
68             auto img = cast(GlImage)sc.images[pr.imageIndex];
69             auto size = sc.size;
70 
71             share.ctx.makeCurrent(surf.handle);
72 
73             if (i == prs.length-1) share.ctx.swapInterval = 1;
74             else share.ctx.swapInterval = 0;
75 
76             import gfx.gl3.error : glCheck;
77 
78             gl.BindFramebuffer(GL_READ_FRAMEBUFFER, readFbo);
79             final switch (img.glType) {
80             case GlImgType.renderBuf:
81                 gl.FramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, img.name);
82                 break;
83             case GlImgType.tex:
84                 gl.FramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, img.name, 0);
85                 break;
86             }
87 
88             gl.BindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
89             gl.BlitFramebuffer(0, 0, size[0], size[1], 0, 0, size[0], size[1],
90                                GL_COLOR_BUFFER_BIT, GL_NEAREST);
91             glCheck(gl, "blit framebuffer");
92 
93             share.ctx.swapBuffers(surf.handle);
94         }
95     }
96 }
97 
98 final class GlCommandPool : CommandPool
99 {
100     import gfx.core.rc : atomicRcCode;
101     import gfx.gl3 : GlShare;
102     import gfx.graal.cmd : CommandBuffer;
103 
104     mixin(atomicRcCode);
105 
106     private GlQueue queue;
107     private GlShare share;
108     private GLuint fbo;
109 
110     this(GlQueue queue) {
111         this.queue = queue;
112         this.share = queue.share;
113         auto gl = share.gl;
114         gl.GenFramebuffers(1, &fbo);
115     }
116     override void dispose() {
117         auto gl = share.gl;
118         gl.DeleteFramebuffers(1, &fbo);
119     }
120 
121     override void reset() {}
122 
123     override CommandBuffer[] allocate(size_t count) {
124         auto bufs = new CommandBuffer[count];
125         foreach (i; 0 .. count) {
126             bufs[i] = new GlCommandBuffer(this, fbo);
127         }
128         return bufs;
129     }
130 
131     override void free(CommandBuffer[] buffers) {}
132 }
133 
134 final class GlCommandBuffer : CommandBuffer
135 {
136     import gfx.core.typecons : Trans;
137     import gfx.core.types : Rect, Viewport;
138     import gfx.gl3 : GlShare, GlInfo;
139     import gfx.gl3.conv : toGl;
140     import gfx.gl3.pipeline : GlPipeline, GlRenderPass;
141     import gfx.graal.buffer : Buffer, IndexType;
142     import gfx.graal.image : ImageBase, ImageLayout, ImageSubresourceRange;
143     import gfx.graal.pipeline : ColorBlendInfo, DepthInfo, DescriptorSet,
144                                 Pipeline, PipelineLayout, Rasterizer,
145                                 ShaderStage, VertexInputBinding,
146                                 VertexInputAttrib, ViewportConfig;
147     import gfx.graal.renderpass : Framebuffer, RenderPass;
148     import std.experimental.logger;
149     import std.typecons : Flag;
150 
151     private enum Dirty {
152         none                = 0x00,
153         vertexBindings      = 0x01,
154         pipeline            = 0x02,
155 
156         all                 = 0xff,
157     }
158 
159     private CommandPool _pool;
160     private GLuint _fbo;
161     private bool _persistent;
162 
163     private GlCommand[] _cmds;
164     private Dirty _dirty;
165 
166     // render pass cache
167     private GlRenderPass _renderPass;
168     private GlColorAttachment[] _attachments; // TODO: static array
169     private uint _subpass;
170 
171     // pipeline cache
172     private GLenum _primitive;
173     private VertexInputBinding[] _inputBindings;
174     private VertexInputAttrib[] _inputAttribs;
175 
176     // index buffer cache
177     private size_t _indexOffset;
178     private GLenum _indexType;
179 
180     // vertex cache
181     private VertexBinding[] _vertexBindings;
182 
183 
184     this (CommandPool pool, GLuint fbo) {
185         _pool = pool;
186         _fbo = fbo;
187     }
188 
189     override @property CommandPool pool() {
190         return _pool;
191     }
192 
193     override void reset() {
194         _cmds.length = 0;
195         _dirty = Dirty.none;
196         _vertexBindings.length = 0;
197     }
198 
199     override void begin(Flag!"persistent" persistent) {
200         _persistent = persistent;
201     }
202     override void end() {}
203 
204     override void pipelineBarrier(Trans!PipelineStage stageTrans,
205                                   BufferMemoryBarrier[] bufMbs,
206                                   ImageMemoryBarrier[] imgMbs)
207     {
208         warningf("unimplemented GL command");
209     }
210 
211     override void clearColorImage(ImageBase image, ImageLayout layout,
212                                   in ClearColorValues clearValues,
213                                   ImageSubresourceRange[] ranges)
214     {
215         import gfx.gl3.resource : GlImage;
216         _cmds ~= new SetupFramebufferCmd(_fbo, cast(GlImage)image);
217         _cmds ~= new ClearColorCmd(clearValues);
218     }
219 
220     override void clearDepthStencilImage(ImageBase image, ImageLayout layout,
221                                          in ClearDepthStencilValues clearValues,
222                                          ImageSubresourceRange[] ranges)
223     {
224         import gfx.gl3.resource : GlImage;
225         _cmds ~= new SetupFramebufferCmd(_fbo, cast(GlImage)image);
226         _cmds ~= new ClearDepthStencilCmd(clearValues, true, true);
227     }
228 
229     override void copyBuffer(Trans!Buffer buffers, CopyRegion[] regions)
230     {
231         import gfx.gl3.resource : GlBuffer;
232         foreach (r; regions) {
233             _cmds ~= new CopyBufToBufCmd(
234                 cast(GlBuffer)buffers.from, cast(GlBuffer)buffers.to, r
235             );
236         }
237     }
238 
239     override void copyBufferToImage(Buffer srcBuffer, ImageBase dstImage,
240                                     in ImageLayout dstLayout,
241                                     in BufferImageCopy[] regions)
242     {
243         import gfx.gl3.resource : GlBuffer, GlImage;
244         foreach (r; regions) {
245             _cmds ~= new CopyBufToImgCmd(
246                 cast(GlBuffer)srcBuffer, cast(GlImage)dstImage, r
247             );
248         }
249     }
250 
251     override void setViewport(in uint firstViewport, in Viewport[] viewports)
252     {
253         _cmds ~= new SetViewportsCmd(firstViewport, viewports);
254     }
255 
256     override void setScissor(in uint firstScissor, in Rect[] scissors)
257     {
258         _cmds ~= new SetScissorsCmd(firstScissor, scissors);
259     }
260 
261     override void setDepthBounds(in float minDepth, in float maxDepth)
262     {
263         warningf("unimplemented GL command");
264     }
265 
266     void setLineWidth(in float lineWidth)
267     {
268         _cmds ~= new SetLineWidthCmd(lineWidth);
269     }
270 
271     override void setDepthBias(in float constFactor, in float clamp, in float slopeFactor)
272     {
273         _cmds ~= new SetDepthBiasCmd(constFactor, clamp, slopeFactor);
274     }
275 
276     override void setStencilCompareMask(in StencilFace faceMask, in uint compareMask)
277     {
278         warningf("unimplemented GL command");
279     }
280 
281     override void setStencilWriteMask(in StencilFace faceMask, in uint writeMask)
282     {
283         warningf("unimplemented GL command");
284     }
285 
286     override void setStencilReference(in StencilFace faceMask, in uint reference)
287     {
288         warningf("unimplemented GL command");
289     }
290 
291     override void setBlendConstants(in float[4] blendConstants)
292     {
293         _cmds ~= new SetBlendConstantsCmd(blendConstants);
294     }
295 
296     override void beginRenderPass(RenderPass rp, Framebuffer fb,
297                                   Rect area, ClearValues[] clearValues)
298     {
299         import gfx.gl3.pipeline : GlFramebuffer;
300         import gfx.graal.pipeline : ColorBlendAttachment;
301         import std.algorithm : map;
302         import std.array : array;
303 
304         _renderPass = cast(GlRenderPass)rp;
305         _attachments = _renderPass.attachments.map!(
306             ad => GlColorAttachment(false, ad, ColorBlendAttachment.init)
307         ).array;
308         setActiveSubpass(0);
309 
310         const glFb = cast(GlFramebuffer)fb;
311         _cmds ~= new BindFramebufferCmd(glFb.name);
312         foreach (cv; clearValues) {
313             if (cv.type == ClearValues.Type.color) {
314                 _cmds ~= new ClearColorCmd(cv.values.color);
315             }
316             if (cv.type == ClearValues.Type.depthStencil) {
317                 _cmds ~= new ClearDepthStencilCmd(cv.values.depthStencil, true, true);
318             }
319         }
320     }
321 
322     override void nextSubpass()
323     {
324         setActiveSubpass(_subpass+1);
325     }
326 
327     override void endRenderPass()
328     {}
329 
330     override void bindPipeline(Pipeline pipeline)
331     {
332         auto glPipeline = cast(GlPipeline)pipeline;
333 
334         _cmds ~= new BindProgramCmd(glPipeline.prog);
335         _cmds ~= new SetViewportConfigsCmd(glPipeline.info.viewports);
336         _cmds ~= new SetRasterizerCmd(glPipeline.info.rasterizer);
337         _cmds ~= new SetDepthInfoCmd(glPipeline.info.depthInfo);
338         _cmds ~= new SetStencilInfoCmd(glPipeline.info.stencilInfo);
339 
340         uint curAttach = 0;
341         foreach (ref a; _attachments) {
342             if (a.enabled) {
343                 a.attachment = glPipeline.info.blendInfo.attachments[curAttach++];
344             }
345         }
346         assert(curAttach == glPipeline.info.blendInfo.attachments.length);
347         _cmds ~= new BindBlendSlotsCmd(_attachments.dup);
348 
349         _primitive = toGl(glPipeline.info.assembly.primitive);
350         _inputBindings = glPipeline.info.inputBindings;
351         _inputAttribs = glPipeline.info.inputAttribs;
352 
353         dirty(Dirty.vertexBindings | Dirty.pipeline);
354     }
355 
356     override void bindVertexBuffers(uint firstBinding, VertexBinding[] bindings)
357     {
358         const minLen = firstBinding + bindings.length;
359         if (_vertexBindings.length < minLen) _vertexBindings.length = minLen;
360         _vertexBindings[firstBinding .. firstBinding+bindings.length] = bindings;
361         dirty(Dirty.vertexBindings);
362     }
363 
364     override void bindIndexBuffer(Buffer indexBuf, size_t offset,
365                                   IndexType type)
366     {
367         import gfx.gl3.resource : GlBuffer;
368         auto glBuf = cast(GlBuffer)indexBuf;
369         _cmds ~= new BindIndexBufCmd(glBuf.name);
370         _indexOffset = offset;
371         _indexType = toGl(type);
372     }
373 
374     override void bindDescriptorSets(PipelineBindPoint bindPoint,
375                                      PipelineLayout layout, uint firstSet,
376                                      DescriptorSet[] sets,
377                                      in size_t[] dynamicOffsets)
378     {
379         import gfx.gl3.pipeline : GlDescriptorSet;
380         import gfx.graal.pipeline : DescriptorType;
381 
382         size_t dynInd = 0;
383 
384         foreach (si, ds; sets) {
385             auto glSet = cast(GlDescriptorSet)ds;
386 
387             foreach(bi, b; glSet.bindings) {
388 
389                 foreach (di, d; b.descriptors) {
390 
391                     switch (b.layout.descriptorType) {
392                     case DescriptorType.uniformBuffer:
393                         _cmds ~= new BindUniformBufferCmd(
394                             b.layout.binding, d.bufferRange, 0
395                         );
396                         break;
397                     case DescriptorType.uniformBufferDynamic:
398                         _cmds ~= new BindUniformBufferCmd(
399                             b.layout.binding, d.bufferRange, dynamicOffsets[dynInd++]
400                         );
401                         break;
402                     case DescriptorType.combinedImageSampler:
403                         _cmds ~= new BindSamplerImageCmd(
404                             b.layout.binding, d.combinedImageSampler
405                         );
406                         break;
407                     default:
408                         warningf("unhandled descriptor set");
409                         break;
410                     }
411                 }
412             }
413         }
414     }
415 
416     override void pushConstants(PipelineLayout layout, ShaderStage stages,
417                                 size_t offset, size_t size, const(void)* data)
418     {
419         warningf("unimplemented GL command");
420     }
421 
422     override void draw(uint vertexCount, uint instanceCount, uint firstVertex,
423                        uint firstInstance)
424     {
425         ensureBindings();
426         _cmds ~= new DrawCmd(
427             _primitive, cast(GLint)firstVertex, cast(GLsizei)vertexCount,
428             cast(GLsizei)instanceCount, cast(GLuint)firstInstance
429         );
430     }
431 
432     override void drawIndexed(uint indexCount, uint instanceCount,
433                               uint firstVertex, int vertexOffset,
434                               uint firstInstance)
435     {
436         ensureBindings();
437 
438         const factor = _indexType == GL_UNSIGNED_SHORT ? 2 : 4;
439         const offset = factor * firstVertex + _indexOffset;
440 
441         _cmds ~= new DrawIndexedCmd(
442             _primitive, cast(GLsizei)indexCount, _indexType, offset, cast(GLint)vertexOffset,
443             cast(GLsizei)instanceCount, cast(GLuint)firstInstance
444         );
445     }
446 
447     private void dirty(Dirty flag) {
448         _dirty |= flag;
449     }
450 
451     private void clean(Dirty flag) {
452         _dirty &= ~flag;
453     }
454 
455     private bool allDirty(Dirty flags) {
456         return (_dirty & flags) == flags;
457     }
458 
459     private bool someDirty(Dirty flags) {
460         return (_dirty & flags) != Dirty.none;
461     }
462 
463     private void ensureBindings() {
464         if (someDirty(Dirty.vertexBindings)) {
465             bindAttribs();
466             clean(Dirty.vertexBindings);
467         }
468     }
469 
470     private void bindAttribs() {
471         assert(_vertexBindings.length == _inputBindings.length);
472         assert(someDirty(Dirty.vertexBindings));
473 
474         import gfx.gl3.conv : glVertexFormat, vertexFormatSupported;
475         import gfx.gl3.resource : GlBuffer;
476         import gfx.graal.format : formatDesc;
477         import std.algorithm : filter;
478 
479         foreach (bi, vb; _vertexBindings) {
480             const bindingInfo = _inputBindings[bi];
481 
482             GlVertexAttrib[] attribs;
483 
484             foreach (ai; _inputAttribs.filter!(ia => ia.binding == bi)) {
485                 const f = ai.format;
486                 assert(vertexFormatSupported(f));
487 
488                 attribs ~= GlVertexAttrib(
489                     ai.location, glVertexFormat(f), vb.offset+ai.offset
490                 );
491             }
492 
493             auto buf = cast(GlBuffer)vb.buffer;
494             _cmds ~= new BindVertexBufCmd(buf.name, cast(GLsizei)bindingInfo.stride, attribs);
495         }
496     }
497 
498     void setActiveSubpass(uint subpass) {
499         assert(_renderPass);
500         assert(_attachments.length == _renderPass.attachments.length);
501         const sp = _renderPass.subpasses[subpass];
502         foreach (ref a; _attachments) {
503             a.enabled = false;
504         }
505         foreach (ar; sp.colors) {
506             _attachments[ar.attachment].enabled = true;
507         }
508         _subpass = subpass;
509     }
510 
511 }
512 
513 private:
514 
515 struct GlState {
516     import gfx.core.types : Rect, Viewport;
517     import gfx.graal.pipeline : DepthInfo, Rasterizer, StencilInfo, ViewportConfig;
518 
519     GLuint prog;
520     const(ViewportConfig)[] vcs;
521     const(Viewport)[] viewports;
522     const(Rect)[] scissors;
523     Rasterizer rasterizer;
524     DepthInfo depthInfo;
525     StencilInfo stencilInfo;
526 }
527 
528 struct GlColorAttachment {
529     import gfx.graal.pipeline : ColorBlendAttachment;
530     import gfx.graal.renderpass : AttachmentDescription;
531 
532     bool enabled;
533     AttachmentDescription desc;
534     ColorBlendAttachment attachment;
535 }
536 
537 abstract class GlCommand {
538     abstract void execute(GlQueue queue, Gl gl);
539 }
540 
541 string allFieldsCtor(T)()
542 {
543     import std.array : join;
544     import std.traits : FieldNameTuple, Fields;
545 
546     alias names = FieldNameTuple!T;
547     alias types = Fields!T;
548 
549     string[] fields;
550     static foreach (i, n; names) {
551         fields ~= types[i].stringof ~ " " ~ n;
552     }
553     string code = "this(" ~ fields.join(", ") ~ ") {\n";
554     static foreach (n; names) {
555         code ~= "    this." ~ n ~ " = " ~ n ~ ";\n";
556     }
557     return code ~ "}";
558 }
559 
560 final class CopyBufToBufCmd : GlCommand
561 {
562     import gfx.gl3.resource : GlBuffer;
563 
564     GlBuffer src;
565     GlBuffer dst;
566     CopyRegion region;
567 
568     mixin(allFieldsCtor!(typeof(this)));
569 
570     override void execute(GlQueue queue, Gl gl) {
571         gl.BindBuffer(GL_COPY_READ_BUFFER, src.name);
572         gl.BindBuffer(GL_COPY_WRITE_BUFFER, dst.name);
573         gl.CopyBufferSubData(
574             GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER,
575             cast(GLintptr)region.offset.from, cast(GLintptr)region.offset.to,
576             cast(GLsizeiptr)region.size
577         );
578         gl.BindBuffer(GL_COPY_READ_BUFFER, 0);
579         gl.BindBuffer(GL_COPY_WRITE_BUFFER, 0);
580 
581         import gfx.gl3.error : glCheck;
582         glCheck(gl, "copy buffer to buffer");
583     }
584 }
585 
586 final class CopyBufToImgCmd : GlCommand
587 {
588     import gfx.gl3.resource : GlBuffer, GlImage;
589 
590     GlBuffer buf;
591     GlImage img;
592     BufferImageCopy region;
593 
594     mixin(allFieldsCtor!(typeof(this)));
595 
596     override void execute(GlQueue queue, Gl gl) {
597         gl.ActiveTexture(GL_TEXTURE0);
598         gl.BindBuffer(GL_PIXEL_UNPACK_BUFFER, buf.name);
599         gl.BindTexture(img.texTarget, img.name);
600         img.texSubImage(region);
601         gl.BindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
602 
603         import gfx.gl3.error : glCheck;
604         glCheck(gl, "copy buffer to image");
605     }
606 }
607 
608 final class BindFramebufferCmd : GlCommand
609 {
610     GLuint fbo;
611     this(GLuint fbo) { this.fbo = fbo; }
612     override void execute(GlQueue queue, Gl gl) {
613         gl.BindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
614     }
615 }
616 
617 final class SetupFramebufferCmd : GlCommand
618 {
619     import gfx.gl3.resource : GlImage, GlImgType;
620 
621     GLuint fbo;
622     GlImage img;
623 
624     this(GLuint fbo, GlImage img) {
625         this.fbo = fbo;
626         this.img = img;
627     }
628 
629     override void execute(GlQueue queue, Gl gl) {
630         gl.BindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
631         final switch (img.glType) {
632         case GlImgType.renderBuf:
633             gl.FramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, img.name);
634             break;
635         case GlImgType.tex:
636             gl.FramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, img.name, 0);
637             break;
638         }
639         const GLenum drawBuf = GL_COLOR_ATTACHMENT0;
640         gl.DrawBuffers(1, &drawBuf);
641     }
642 }
643 
644 final class SetViewportConfigsCmd : GlCommand
645 {
646     import gfx.graal.pipeline : ViewportConfig;
647     ViewportConfig[] viewports;
648     this (ViewportConfig[] viewports) {
649         this.viewports = viewports;
650     }
651 
652     override void execute(GlQueue queue, Gl gl) {
653 
654         if (queue.state.vcs == viewports) return;
655 
656         if (viewports.length > 1 && !queue.info.viewportArray) {
657             import std.experimental.logger : error;
658             error("ARB_viewport_array not supported");
659             viewports = viewports[0..1];
660         }
661 
662         if (viewports.length > 1) {
663             foreach (i, vc; viewports) {
664                 const vp = vc.viewport;
665                 gl.ViewportIndexedf(cast(GLuint)i, vp.x, vp.y, vp.width, vp.height);
666                 gl.DepthRangeIndexed(cast(GLuint)i, vp.minDepth, vp.maxDepth);
667 
668                 const sc = vc.scissors;
669                 gl.ScissorIndexed(
670                     cast(GLuint)i,
671                     cast(GLint)sc.x, cast(GLint)sc.y,
672                     cast(GLsizei)sc.width, cast(GLsizei)sc.height
673                 );
674             }
675         }
676         else if (viewports.length == 1) {
677             const vp = viewports[0].viewport;
678             gl.Viewport(
679                 cast(GLint)vp.x, cast(GLint)vp.y,
680                 cast(GLsizei)vp.width, cast(GLsizei)vp.height
681             );
682             gl.DepthRangef(vp.minDepth, vp.maxDepth);
683 
684             const sc = viewports[0].scissors;
685             gl.Scissor(
686                 cast(GLint)sc.x, cast(GLint)sc.y,
687                 cast(GLsizei)sc.width, cast(GLsizei)sc.height
688             );
689         }
690 
691         queue.state.vcs = viewports;
692     }
693 }
694 
695 final class SetViewportsCmd : GlCommand
696 {
697     import gfx.core.types : Viewport;
698 
699     uint firstViewport;
700     const(Viewport)[] viewports;
701 
702     this (in uint firstViewport, const(Viewport)[] viewports) {
703         this.firstViewport = firstViewport;
704         this.viewports = viewports;
705     }
706 
707     override void execute(GlQueue queue, Gl gl) {
708 
709         if (queue.state.viewports == viewports) return;
710 
711         bool useArray = viewports.length > 1 || firstViewport > 0;
712 
713         if (useArray && !queue.info.viewportArray) {
714             import std.experimental.logger : error;
715             error("ARB_viewport_array not supported");
716             viewports = viewports[0..1];
717             firstViewport = 0;
718             useArray = false;
719         }
720 
721         if (useArray) {
722             foreach (i, vp; viewports) {
723                 gl.ViewportIndexedf(cast(GLuint)(i+firstViewport), vp.x, vp.y, vp.width, vp.height);
724                 gl.DepthRangeIndexed(cast(GLuint)(i+firstViewport), vp.minDepth, vp.maxDepth);
725             }
726         }
727         else if (viewports.length == 1) {
728             const vp = viewports[0];
729             gl.Viewport(
730                 cast(GLint)vp.x, cast(GLint)vp.y,
731                 cast(GLsizei)vp.width, cast(GLsizei)vp.height
732             );
733             gl.DepthRangef(vp.minDepth, vp.maxDepth);
734         }
735 
736         queue.state.viewports = viewports;
737     }
738 }
739 
740 final class SetScissorsCmd : GlCommand
741 {
742     import gfx.core.types : Rect;
743 
744     uint firstScissor;
745     const(Rect)[] scissors;
746 
747     this (in uint firstScissor, const(Rect)[] scissors) {
748         this.firstScissor = firstScissor;
749         this.scissors = scissors;
750     }
751 
752     override void execute(GlQueue queue, Gl gl)
753     {
754         import std.algorithm : equal, map;
755 
756         if (queue.state.scissors == scissors) return;
757 
758         bool useArray = scissors.length > 1 || firstScissor > 0;
759         if (useArray && !queue.info.viewportArray) {
760             import std.experimental.logger : error;
761             error("ARB_viewport_array not supported");
762             scissors = scissors[0..1];
763             firstScissor = 0;
764             useArray = false;
765         }
766 
767         if (useArray) {
768             foreach (i, sc; scissors) {
769                 gl.ScissorIndexed(
770                     cast(GLuint)(i+firstScissor),
771                     cast(GLint)sc.x, cast(GLint)sc.y,
772                     cast(GLsizei)sc.width, cast(GLsizei)sc.height
773                 );
774             }
775         }
776         else if (scissors.length == 1) {
777             const sc = scissors[0];
778             gl.Scissor(
779                 cast(GLint)sc.x, cast(GLint)sc.y,
780                 cast(GLsizei)sc.width, cast(GLsizei)sc.height
781             );
782         }
783 
784         queue.state.scissors = scissors;
785     }
786 }
787 
788 final class SetLineWidthCmd : GlCommand
789 {
790     float lineWidth;
791     this (in float lineWidth) {
792         this.lineWidth = lineWidth;
793     }
794     override void execute(GlQueue queue, Gl gl) {
795         gl.LineWidth(this.lineWidth);
796     }
797 }
798 
799 final class SetDepthBiasCmd : GlCommand
800 {
801     float constFactor;
802     float clamp;
803     float slopeFactor;
804 
805     mixin(allFieldsCtor!(typeof(this)));
806 
807     override void execute(GlQueue queue, Gl gl)
808     {
809         if (queue.info.polygonOffsetClamp) {
810             gl.PolygonOffsetClamp(slopeFactor, constFactor, clamp);
811         }
812         else {
813             gl.PolygonOffset(slopeFactor, constFactor);
814         }
815     }
816 }
817 
818 final class SetBlendConstantsCmd : GlCommand
819 {
820     float[4] constants;
821 
822     mixin(allFieldsCtor!(typeof(this)));
823 
824     override void execute(GlQueue queue, Gl gl)
825     {
826         gl.BlendColor(constants[0], constants[1], constants[2], constants[3]);
827     }
828 }
829 
830 final class ClearColorCmd : GlCommand {
831 
832     ClearColorValues values;
833 
834     this (ClearColorValues values) {
835         this.values = values;
836     }
837 
838     override void execute(GlQueue queue, Gl gl) {
839         gl.ColorMaski(0, GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
840         final switch (values.type) {
841         case ClearColorValues.Type.f32:
842             gl.ClearBufferfv(GL_COLOR, 0, &values.values.f32[0]);
843             break;
844         case ClearColorValues.Type.i32:
845             gl.ClearBufferiv(GL_COLOR, 0, &values.values.i32[0]);
846             break;
847         case ClearColorValues.Type.u32:
848             gl.ClearBufferuiv(GL_COLOR, 0, &values.values.u32[0]);
849             break;
850         }
851     }
852 }
853 
854 final class ClearDepthStencilCmd : GlCommand {
855 
856     ClearDepthStencilValues values;
857     bool depth;
858     bool stencil;
859 
860     this (ClearDepthStencilValues values, bool depth, bool stencil) {
861         this.values = values;
862         this.depth = depth;
863         this.stencil = stencil;
864     }
865 
866     override void execute(GlQueue queue, Gl gl) {
867         if (depth) {
868             gl.DepthMask(GL_TRUE);
869             gl.ClearBufferfv(GL_DEPTH, 0, &values.depth);
870         }
871         if (stencil) {
872             const val = cast(GLint)values.stencil;
873             gl.StencilMask(GLuint.max);
874             gl.ClearBufferiv(GL_STENCIL, 0, &val);
875         }
876     }
877 }
878 
879 final class BindProgramCmd : GlCommand
880 {
881     GLuint prog;
882     this(GLuint prog) { this.prog = prog; }
883     override void execute(GlQueue queue, Gl gl) {
884         if (queue.state.prog == prog) return;
885         gl.UseProgram(prog);
886         queue.state.prog = prog;
887     }
888 }
889 
890 final class SetRasterizerCmd : GlCommand
891 {
892     import gfx.graal.pipeline : Rasterizer;
893     Rasterizer rasterizer;
894     this(Rasterizer rasterizer) { this.rasterizer = rasterizer; }
895 
896     override void execute(GlQueue queue, Gl gl) {
897         import gfx.gl3.conv : toGl;
898         import gfx.graal.pipeline : Cull, PolygonMode;
899 
900         if (queue.state.rasterizer == rasterizer) return;
901 
902         void polygonBias(GLenum polygonMode, GLenum depthBias)
903         {
904             gl.PolygonMode(GL_FRONT_AND_BACK, polygonMode);
905             if (rasterizer.depthBias.isSome) {
906                 const db = rasterizer.depthBias.get;
907                 gl.Enable(depthBias);
908                 if (queue.info.polygonOffsetClamp) {
909                     gl.PolygonOffsetClamp(db.slopeFactor, db.constantFactor, db.clamp);
910                 }
911                 else {
912                     gl.PolygonOffset(db.slopeFactor, db.constantFactor);
913                 }
914             }
915             else {
916                 gl.Disable(depthBias);
917             }
918         }
919 
920         final switch (rasterizer.mode) {
921         case PolygonMode.point:
922             polygonBias(GL_POINT, GL_POLYGON_OFFSET_POINT);
923             break;
924         case PolygonMode.line:
925             polygonBias(GL_LINE, GL_POLYGON_OFFSET_LINE);
926             gl.LineWidth(rasterizer.lineWidth);
927             break;
928         case PolygonMode.fill:
929             polygonBias(GL_FILL, GL_POLYGON_OFFSET_FILL);
930             break;
931         }
932 
933         if (rasterizer.cull == Cull.none) {
934             gl.Disable(GL_CULL_FACE);
935         }
936         else {
937             gl.Enable(GL_CULL_FACE);
938             switch (rasterizer.cull) {
939             case Cull.back:
940                 gl.CullFace(GL_BACK);
941                 break;
942             case Cull.front:
943                 gl.CullFace(GL_FRONT);
944                 break;
945             case Cull.frontAndBack:
946                 gl.CullFace(GL_FRONT_AND_BACK);
947                 break;
948             default: break;
949             }
950             gl.FrontFace(toGl(rasterizer.front));
951         }
952 
953         if (rasterizer.depthClamp) {
954             gl.Enable(GL_DEPTH_CLAMP);
955         }
956         else {
957             gl.Disable(GL_DEPTH_CLAMP);
958         }
959 
960         queue.state.rasterizer = rasterizer;
961     }
962 }
963 
964 final class SetDepthInfoCmd : GlCommand
965 {
966     import gfx.graal.pipeline : DepthInfo;
967 
968     DepthInfo info;
969     this (DepthInfo info) { this.info = info; }
970 
971     override void execute(GlQueue queue, Gl gl) {
972         import gfx.gl3.conv : toGl;
973 
974         if (queue.state.depthInfo == info) return;
975 
976         if (info.enabled) {
977             gl.Enable(GL_DEPTH_TEST);
978             gl.DepthFunc(toGl(info.compareOp));
979             gl.DepthMask(cast(GLboolean)info.write);
980             if (info.boundsTest) {
981                 import std.experimental.logger : warningf;
982                 warningf("no support for depth bounds test");
983             }
984         }
985         else {
986             gl.Disable(GL_DEPTH_TEST);
987         }
988 
989         queue.state.depthInfo = info;
990     }
991 }
992 
993 final class SetStencilInfoCmd : GlCommand
994 {
995     import gfx.graal.pipeline : StencilInfo;
996 
997     StencilInfo info;
998     this(StencilInfo info) { this.info = info; }
999 
1000     override void execute(GlQueue queue, Gl gl) {
1001         import gfx.gl3.conv : toGl;
1002         import gfx.graal.pipeline : StencilOpState;
1003 
1004         if (queue.state.stencilInfo == info) return;
1005 
1006         if (info.enabled) {
1007             gl.Enable(GL_STENCIL_TEST);
1008             void bindFace(GLenum face, StencilOpState state) {
1009                 gl.StencilOpSeparate(
1010                     face, toGl(state.failOp), toGl(state.depthFailOp), toGl(state.passOp)
1011                 );
1012                 gl.StencilFuncSeparate(
1013                     face, toGl(state.compareOp), state.refMask, state.compareMask
1014                 );
1015                 gl.StencilMaskSeparate(
1016                     face, state.writeMask
1017                 );
1018             }
1019             bindFace(GL_FRONT, info.front);
1020             bindFace(GL_BACK, info.back);
1021         }
1022         else {
1023             gl.Disable(GL_STENCIL_TEST);
1024         }
1025     }
1026 }
1027 
1028 struct GlVertexAttrib {
1029     import gfx.gl3.conv : GlVertexFormat;
1030 
1031     GLuint index;
1032     GlVertexFormat format;
1033     size_t offset;
1034 }
1035 
1036 final class BindVertexBufCmd : GlCommand
1037 {
1038     GLuint buffer;
1039     GLsizei stride;
1040     GlVertexAttrib[] attribs;
1041 
1042     this(GLuint buffer, GLsizei stride, GlVertexAttrib[] attribs) {
1043         this.buffer = buffer;
1044         this.stride = stride;
1045         this.attribs = attribs;
1046     }
1047 
1048     override void execute(GlQueue queue, Gl gl)
1049     {
1050         import gfx.gl3.conv : VAOAttribFun;
1051 
1052         gl.BindBuffer(GL_ARRAY_BUFFER, buffer);
1053         foreach(at; attribs) {
1054             final switch (at.format.fun) {
1055             case VAOAttribFun.f:
1056                 gl.VertexAttribPointer(
1057                     at.index, at.format.size, at.format.type,
1058                     at.format.normalized, stride, cast(const(void*))at.offset
1059                 );
1060                 break;
1061             case VAOAttribFun.i:
1062                 gl.VertexAttribIPointer(
1063                     at.index, at.format.size, at.format.type, stride,
1064                     cast(const(void*))at.offset
1065                 );
1066                 break;
1067             case VAOAttribFun.d:
1068                 gl.VertexAttribLPointer(
1069                     at.index, at.format.size, at.format.type, stride,
1070                     cast(const(void*))at.offset
1071                 );
1072                 break;
1073             }
1074             gl.EnableVertexAttribArray(at.index);
1075         }
1076         gl.BindBuffer(GL_ARRAY_BUFFER, 0);
1077     }
1078 }
1079 
1080 final class BindIndexBufCmd : GlCommand
1081 {
1082     GLuint buf;
1083 
1084     this(GLuint buf) {
1085         this.buf = buf;
1086     }
1087 
1088     override void execute(GlQueue queue, Gl gl)
1089     {
1090         gl.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, buf);
1091     }
1092 }
1093 
1094 final class BindUniformBufferCmd : GlCommand
1095 {
1096     import gfx.gl3.resource : GlBuffer;
1097     import gfx.graal.pipeline : BufferRange;
1098 
1099     GLuint binding;
1100     BufferRange bufferRange;
1101     size_t dynamicOffset;
1102 
1103     this (GLuint binding, BufferRange br, in size_t dynOffset) {
1104         this.binding = binding;
1105         this.bufferRange = br;
1106         this.dynamicOffset = dynOffset;
1107     }
1108 
1109     override void execute(GlQueue queue, Gl gl) {
1110         auto glBuf = cast(GlBuffer)bufferRange.buffer;
1111 
1112         gl.BindBufferRange(
1113             GL_UNIFORM_BUFFER, binding, glBuf.name,
1114             cast(GLintptr)(bufferRange.offset+dynamicOffset),
1115             cast(GLintptr)bufferRange.range
1116         );
1117     }
1118 }
1119 
1120 final class BindSamplerImageCmd : GlCommand
1121 {
1122     import gfx.graal.pipeline : CombinedImageSampler;
1123 
1124     GLuint binding;
1125     CombinedImageSampler cis;
1126 
1127     mixin(allFieldsCtor!(typeof(this)));
1128 
1129     override void execute(GlQueue queue, Gl gl) {
1130         import gfx.gl3.error : glCheck;
1131         import gfx.gl3.resource : GlImageView, GlSampler;
1132 
1133         auto view = cast(GlImageView)cis.view;
1134         auto sampler = cast(GlSampler)cis.sampler;
1135 
1136         gl.ActiveTexture(GL_TEXTURE0 + binding);
1137         gl.BindTexture(view.target, view.name);
1138         glCheck(gl, "bind texture");
1139 
1140         sampler.bind(view.target, binding);
1141         glCheck(gl, "bind sampler");
1142     }
1143 }
1144 
1145 final class BindBlendSlotsCmd : GlCommand
1146 {
1147     import gfx.graal.pipeline : ColorMask;
1148 
1149     GlColorAttachment[] attachments;
1150 
1151     this(GlColorAttachment[] attachments) {
1152         this.attachments = attachments;
1153     }
1154 
1155     override void execute(GlQueue queue, Gl gl) {
1156         foreach (GLuint slot, a; attachments) {
1157             if (a.enabled) {
1158                 // TODO logicOp
1159                 if (a.attachment.enabled) {
1160                     // TODO
1161                     import std.experimental.logger : warning;
1162                     warning("Gl blending not implemented");
1163                 }
1164                 else {
1165                     gl.Disablei(GL_BLEND, slot);
1166                 }
1167                 import std.typecons : BitFlags, Yes;
1168                 BitFlags!(ColorMask, Yes.unsafe) cm = a.attachment.colorMask;
1169                 gl.ColorMaski(
1170                     slot,
1171                     (cm & ColorMask.r) ? GL_TRUE : GL_FALSE,
1172                     (cm & ColorMask.g) ? GL_TRUE : GL_FALSE,
1173                     (cm & ColorMask.b) ? GL_TRUE : GL_FALSE,
1174                     (cm & ColorMask.a) ? GL_TRUE : GL_FALSE
1175                 );
1176             }
1177             else {
1178                 gl.ColorMaski(
1179                     slot, GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE
1180                 );
1181             }
1182         }
1183     }
1184 }
1185 
1186 final class DrawCmd : GlCommand
1187 {
1188     GLenum primitive;
1189     GLint first;
1190     GLsizei count;
1191     GLsizei instanceCount;
1192     GLuint baseInstance;
1193 
1194     this (GLenum primitive, GLint first, GLsizei count, GLsizei instanceCount,
1195             GLuint baseInstance)
1196     {
1197         this.primitive = primitive;
1198         this.first = first;
1199         this.count = count;
1200         this.instanceCount = instanceCount;
1201         this.baseInstance = baseInstance;
1202     }
1203 
1204     override void execute(GlQueue queue, Gl gl) {
1205         if (baseInstance != 0 && !queue.info.baseInstance) {
1206             import std.experimental.logger : errorf;
1207             errorf("No support for ARB_base_instance");
1208             return;
1209         }
1210         if (instanceCount <= 1) {
1211             gl.DrawArrays(primitive, first, count);
1212         }
1213         else if (instanceCount > 1 && baseInstance == 0) {
1214             gl.DrawArraysInstanced(primitive, first, count, instanceCount);
1215         }
1216         else if (instanceCount > 1 && baseInstance != 0) {
1217             gl.DrawArraysInstancedBaseInstance(
1218                 primitive, first, count, instanceCount, baseInstance
1219             );
1220         }
1221         import gfx.gl3.error : glCheck;
1222         glCheck(gl, "draw");
1223     }
1224 }
1225 
1226 final class DrawIndexedCmd : GlCommand
1227 {
1228     GLenum primitive;
1229     GLsizei count;
1230     GLenum type;
1231     size_t indexBufOffset;
1232     GLint baseVertex;
1233     GLsizei instanceCount;
1234     GLuint baseInstance;
1235 
1236     this(GLenum primitive, GLsizei count, GLenum type, size_t indexBufOffset,
1237             GLint baseVertex, GLsizei instanceCount, GLuint baseInstance)
1238     {
1239         this.primitive = primitive;
1240         this.count = count;
1241         this.type = type;
1242         this.indexBufOffset = indexBufOffset;
1243         this.baseVertex = baseVertex;
1244         this.instanceCount = instanceCount;
1245         this.baseInstance = baseInstance;
1246     }
1247 
1248     override void execute(GlQueue queue, Gl gl) {
1249         import std.experimental.logger : errorf;
1250 
1251         const offset = cast(const(void*))indexBufOffset;
1252 
1253         if (baseVertex != 0 && !queue.info.drawElementsBaseVertex) {
1254             errorf("No support for ARB_draw_elements_base_vertex");
1255             return;
1256         }
1257         if (baseInstance != 0 && !queue.info.baseInstance) {
1258             errorf("No support for ARB_base_instance");
1259             return;
1260         }
1261 
1262         if (instanceCount <= 1 && baseVertex == 0) {
1263             gl.DrawElements(primitive, count, type, offset);
1264         }
1265         else if (instanceCount <= 1 && baseVertex != 0) {
1266             gl.DrawElementsBaseVertex(primitive, count, type, offset, baseVertex);
1267         }
1268         else if (instanceCount > 1 && baseInstance == 0 && baseVertex == 0) {
1269             gl.DrawElementsInstanced(primitive, count, type, offset, instanceCount);
1270         }
1271         else if (instanceCount > 1 && baseInstance == 0 && baseVertex != 0) {
1272             gl.DrawElementsInstancedBaseVertex(
1273                 primitive, count, type, offset, instanceCount, baseVertex
1274             );
1275         }
1276         else if (instanceCount > 1 && baseInstance != 0 && baseVertex == 0) {
1277             gl.DrawElementsInstancedBaseInstance(
1278                 primitive, count, type, offset, instanceCount, baseInstance
1279             );
1280         }
1281         else {
1282             gl.DrawElementsInstancedBaseVertexBaseInstance(
1283                 primitive, count, type, offset, instanceCount, baseVertex, baseInstance
1284             );
1285         }
1286         import gfx.gl3.error : glCheck;
1287         glCheck(gl, "draw indexed");
1288     }
1289 }