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