• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

orion-ecs / keen-eye / 20890285108

11 Jan 2026 05:42AM UTC coverage: 85.407% (-0.07%) from 85.48%
20890285108

push

github

tyevco
feat(shaders): Add KESL compiler improvements and HLSL backend

This commit implements four KESL (KeenEyes Shader Language) features:

1. #579 Improved Error Messages:
   - Add SourceSpan for multi-character error location tracking
   - Add SuggestionEngine with Levenshtein distance for "did you mean?" suggestions
   - Enhance DiagnosticFormatter with span underlining and suggestions

2. #577 Runtime Abstractions:
   - Create KeenEyes.Shaders package with GPU interfaces
   - Add IGpuDevice, GpuBuffer, CompiledShader, GpuCommandBuffer
   - Add QueryDescriptor for ECS query integration

3. #581 HLSL Backend:
   - Add IShaderGenerator interface for code generators
   - Implement HlslGenerator with DirectX-specific syntax
   - Support float2/3/4, StructuredBuffer, register() bindings
   - Map GLSL functions to HLSL equivalents (fract->frac, mix->lerp)

4. #580 IDE Support:
   - Create VS Code extension for KESL syntax highlighting
   - Add TextMate grammar with full language coverage
   - Include 20 code snippets for common patterns

Closes #577, #579, #580, #581

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

9535 of 13475 branches covered (70.76%)

Branch coverage included in aggregate %.

782 of 970 new or added lines in 10 files covered. (80.62%)

373 existing lines in 8 files now uncovered.

165265 of 191191 relevant lines covered (86.44%)

0.99 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

78.05
/src/KeenEyes.Testing/Graphics/MockGraphicsDevice.cs
1
using System.Numerics;
2
using KeenEyes.Graphics.Abstractions;
3

4
namespace KeenEyes.Testing.Graphics;
5

6
/// <summary>
7
/// A mock implementation of <see cref="IGraphicsDevice"/> for testing GPU operations
8
/// without a real graphics context.
9
/// </summary>
10
/// <remarks>
11
/// <para>
12
/// MockGraphicsDevice tracks all GPU operations and state changes, enabling verification
13
/// of rendering code without actual GPU calls. All operations are recorded for later
14
/// assertion in tests.
15
/// </para>
16
/// <para>
17
/// Use the tracking collections (<see cref="DrawCalls"/>, <see cref="Buffers"/>, etc.)
18
/// and counters to verify that your rendering code is making the expected calls.
19
/// </para>
20
/// </remarks>
21
/// <example>
22
/// <code>
23
/// var device = new MockGraphicsDevice();
24
///
25
/// // Set up a shader
26
/// var program = device.CreateProgram();
27
/// device.UseProgram(program);
28
///
29
/// // Draw something
30
/// device.DrawElements(PrimitiveType.Triangles, 36, IndexType.UnsignedInt);
31
///
32
/// // Verify
33
/// device.DrawCalls.Should().HaveCount(1);
34
/// device.BoundProgram.Should().Be(program);
35
/// </code>
36
/// </example>
37
public sealed class MockGraphicsDevice : IGraphicsDevice
38
{
39
    private uint nextHandle = 1;
2✔
40
    private bool disposed;
41

42
    #region State Tracking
43

44
    /// <summary>
45
    /// Gets the list of all draw calls made to this device.
46
    /// </summary>
47
    public List<DrawCall> DrawCalls { get; } = [];
2✔
48

49
    /// <summary>
50
    /// Gets the dictionary of created buffers by handle.
51
    /// </summary>
52
    public Dictionary<uint, MockBuffer> Buffers { get; } = [];
2✔
53

54
    /// <summary>
55
    /// Gets the dictionary of created textures by handle.
56
    /// </summary>
57
    public Dictionary<uint, MockTexture> Textures { get; } = [];
2✔
58

59
    /// <summary>
60
    /// Gets the dictionary of created shaders by handle.
61
    /// </summary>
62
    public Dictionary<uint, MockShader> Shaders { get; } = [];
2✔
63

64
    /// <summary>
65
    /// Gets the dictionary of created programs by handle.
66
    /// </summary>
67
    public Dictionary<uint, MockProgram> Programs { get; } = [];
2✔
68

69
    /// <summary>
70
    /// Gets the dictionary of created VAOs by handle.
71
    /// </summary>
72
    public Dictionary<uint, MockVao> VAOs { get; } = [];
2✔
73

74
    /// <summary>
75
    /// Gets the current render state.
76
    /// </summary>
77
    public MockRenderState RenderState { get; } = new();
2✔
78

79
    /// <summary>
80
    /// Gets the currently bound VAO, or null if none.
81
    /// </summary>
82
    public uint? BoundVAO { get; private set; }
1✔
83

84
    /// <summary>
85
    /// Gets the currently bound shader program, or null if none.
86
    /// </summary>
87
    public uint? BoundProgram { get; private set; }
1✔
88

89
    /// <summary>
90
    /// Gets the dictionary of bound buffers by target.
91
    /// </summary>
92
    public Dictionary<BufferTarget, uint> BoundBuffers { get; } = [];
2✔
93

94
    /// <summary>
95
    /// Gets the dictionary of bound textures by unit.
96
    /// </summary>
97
    public Dictionary<TextureUnit, uint> BoundTextures { get; } = [];
2✔
98

99
    /// <summary>
100
    /// Gets the current active texture unit.
101
    /// </summary>
102
    public TextureUnit ActiveTextureUnit { get; private set; } = TextureUnit.Texture0;
1✔
103

104
    #endregion
105

106
    #region Counters
107

108
    /// <summary>
109
    /// Gets the number of buffers created.
110
    /// </summary>
111
    public int CreateBufferCount => Buffers.Count;
1✔
112

113
    /// <summary>
114
    /// Gets the number of textures created.
115
    /// </summary>
116
    public int CreateTextureCount => Textures.Count;
1✔
117

118
    /// <summary>
119
    /// Gets the number of shaders created.
120
    /// </summary>
121
    public int CreateShaderCount => Shaders.Count;
1✔
122

123
    /// <summary>
124
    /// Gets the number of programs created.
125
    /// </summary>
126
    public int CreateProgramCount => Programs.Count;
1✔
127

128
    /// <summary>
129
    /// Gets the number of draw calls made.
130
    /// </summary>
131
    public int DrawCount => DrawCalls.Count;
1✔
132

133
    #endregion
134

135
    #region Configuration
136

137
    /// <summary>
138
    /// Gets or sets whether texture loads should fail.
139
    /// </summary>
140
    public bool ShouldFailTextureLoad { get; set; }
1✔
141

142
    /// <summary>
143
    /// Gets or sets whether shader compilation should fail.
144
    /// </summary>
145
    public bool ShouldFailShaderCompile { get; set; }
1✔
146

147
    /// <summary>
148
    /// Gets or sets whether program linking should fail.
149
    /// </summary>
150
    public bool ShouldFailProgramLink { get; set; }
1✔
151

152
    /// <summary>
153
    /// Gets or sets the simulated error code returned by <see cref="GetError"/>.
154
    /// </summary>
155
    public int SimulatedErrorCode { get; set; }
1✔
156

157
    /// <summary>
158
    /// Gets or sets the simulated framebuffer data returned by <see cref="ReadFramebuffer"/>.
159
    /// </summary>
160
    /// <remarks>
161
    /// Set this property to provide pixel data that <see cref="ReadFramebuffer"/> will copy
162
    /// to the output span. The data should be in RGBA format with 4 bytes per pixel.
163
    /// </remarks>
164
    public byte[]? SimulatedFramebufferData { get; set; }
2✔
165

166
    /// <summary>
167
    /// Gets or sets the simulated framebuffer width.
168
    /// </summary>
169
    public int SimulatedFramebufferWidth { get; set; }
2✔
170

171
    /// <summary>
172
    /// Gets or sets the simulated framebuffer height.
173
    /// </summary>
174
    public int SimulatedFramebufferHeight { get; set; }
2✔
175

176
    #endregion
177

178
    #region Test Control
179

180
    /// <summary>
181
    /// Resets all tracking state and counters.
182
    /// </summary>
183
    public void Reset()
184
    {
185
        DrawCalls.Clear();
1✔
186
        Buffers.Clear();
1✔
187
        Textures.Clear();
1✔
188
        Shaders.Clear();
1✔
189
        Programs.Clear();
1✔
190
        VAOs.Clear();
1✔
191
        Framebuffers.Clear();
1✔
192
        Renderbuffers.Clear();
1✔
193
        BoundBuffers.Clear();
1✔
194
        BoundTextures.Clear();
1✔
195
        BoundVAO = null;
1✔
196
        BoundProgram = null;
1✔
197
        BoundFramebuffer = null;
1✔
198
        BoundRenderbuffer = null;
1✔
199
        ActiveTextureUnit = TextureUnit.Texture0;
1✔
200
        RenderState.Reset();
1✔
201
        nextHandle = 1;
1✔
202
        ShouldFailTextureLoad = false;
1✔
203
        ShouldFailShaderCompile = false;
1✔
204
        ShouldFailProgramLink = false;
1✔
205
        SimulatedErrorCode = 0;
1✔
206
        SimulatedFramebufferData = null;
1✔
207
        SimulatedFramebufferWidth = 0;
1✔
208
        SimulatedFramebufferHeight = 0;
1✔
209
        SimulatedFramebufferStatus = FramebufferStatus.Complete;
1✔
210
    }
1✔
211

212
    /// <summary>
213
    /// Clears only the draw calls, keeping other state.
214
    /// </summary>
215
    public void ClearDrawCalls()
216
    {
217
        DrawCalls.Clear();
1✔
218
    }
1✔
219

220
    #endregion
221

222
    #region Buffer Operations
223

224
    /// <inheritdoc />
225
    public uint GenVertexArray()
226
    {
227
        var handle = nextHandle++;
1✔
228
        VAOs[handle] = new MockVao(handle);
1✔
229
        return handle;
1✔
230
    }
231

232
    /// <inheritdoc />
233
    public uint GenBuffer()
234
    {
235
        var handle = nextHandle++;
1✔
236
        Buffers[handle] = new MockBuffer(handle);
1✔
237
        return handle;
1✔
238
    }
239

240
    /// <inheritdoc />
241
    public void BindVertexArray(uint vao)
242
    {
243
        BoundVAO = vao == 0 ? null : vao;
1✔
244
    }
1✔
245

246
    /// <inheritdoc />
247
    public void BindBuffer(BufferTarget target, uint buffer)
248
    {
249
        if (buffer == 0)
1✔
250
        {
251
            BoundBuffers.Remove(target);
1✔
252
        }
253
        else
254
        {
255
            BoundBuffers[target] = buffer;
1✔
256
        }
257
    }
1✔
258

259
    /// <inheritdoc />
260
    public void BufferData(BufferTarget target, ReadOnlySpan<byte> data, BufferUsage usage)
261
    {
262
        if (BoundBuffers.TryGetValue(target, out var handle) && Buffers.TryGetValue(handle, out var buffer))
1✔
263
        {
264
            buffer.Data = data.ToArray();
1✔
265
            buffer.Usage = usage;
1✔
266
            buffer.Target = target;
1✔
267
        }
268
    }
1✔
269

270
    /// <inheritdoc />
271
    public void DeleteVertexArray(uint vao)
272
    {
273
        VAOs.Remove(vao);
1✔
274
        if (BoundVAO == vao)
1✔
275
        {
276
            BoundVAO = null;
1✔
277
        }
278
    }
1✔
279

280
    /// <inheritdoc />
281
    public void DeleteBuffer(uint buffer)
282
    {
283
        Buffers.Remove(buffer);
1✔
284
        foreach (var target in BoundBuffers.Where(kv => kv.Value == buffer).Select(kv => kv.Key).ToList())
1✔
285
        {
286
            BoundBuffers.Remove(target);
1✔
287
        }
288
    }
1✔
289

290
    /// <inheritdoc />
291
    public void EnableVertexAttribArray(uint index)
292
    {
293
        if (BoundVAO.HasValue && VAOs.TryGetValue(BoundVAO.Value, out var vao))
1✔
294
        {
295
            vao.EnabledAttributes.Add(index);
1✔
296
        }
297
    }
1✔
298

299
    /// <inheritdoc />
300
    public void VertexAttribPointer(uint index, int size, VertexAttribType type, bool normalized, uint stride, nuint offset)
301
    {
302
        if (BoundVAO.HasValue && VAOs.TryGetValue(BoundVAO.Value, out var vao))
1✔
303
        {
304
            vao.Attributes[index] = new VertexAttribute(index, size, type, normalized, stride, offset);
1✔
305
        }
306
    }
1✔
307

308
    /// <inheritdoc />
309
    public void VertexAttribDivisor(uint index, uint divisor)
310
    {
UNCOV
311
        if (BoundVAO.HasValue && VAOs.TryGetValue(BoundVAO.Value, out var vao))
×
312
        {
UNCOV
313
            vao.AttributeDivisors[index] = divisor;
×
314
        }
315
    }
×
316

317
    /// <inheritdoc />
318
    public void BufferSubData(BufferTarget target, nint offset, ReadOnlySpan<byte> data)
319
    {
UNCOV
320
        if (BoundBuffers.TryGetValue(target, out var handle) &&
×
321
            Buffers.TryGetValue(handle, out var buffer) &&
×
UNCOV
322
            buffer.Data is not null &&
×
323
            offset >= 0 &&
×
UNCOV
324
            offset + data.Length <= buffer.Data.Length)
×
325
        {
UNCOV
326
            data.CopyTo(buffer.Data.AsSpan((int)offset));
×
327
        }
UNCOV
328
    }
×
329

330
    #endregion
331

332
    #region Texture Operations
333

334
    /// <inheritdoc />
335
    public uint GenTexture()
336
    {
337
        var handle = nextHandle++;
1✔
338
        Textures[handle] = new MockTexture(handle);
1✔
339
        return handle;
1✔
340
    }
341

342
    /// <inheritdoc />
343
    public void BindTexture(TextureTarget target, uint texture)
344
    {
345
        if (texture == 0)
1✔
346
        {
347
            BoundTextures.Remove(ActiveTextureUnit);
1✔
348
        }
349
        else
350
        {
351
            BoundTextures[ActiveTextureUnit] = texture;
1✔
352
            if (Textures.TryGetValue(texture, out var tex))
1✔
353
            {
354
                tex.Target = target;
1✔
355
            }
356
        }
357
    }
1✔
358

359
    /// <inheritdoc />
360
    public void TexImage2D(TextureTarget target, int level, int width, int height, PixelFormat format, ReadOnlySpan<byte> data)
361
    {
362
        if (ShouldFailTextureLoad)
1✔
363
        {
364
            SimulatedErrorCode = 1;
1✔
365
            return;
1✔
366
        }
367

368
        if (BoundTextures.TryGetValue(ActiveTextureUnit, out var handle) && Textures.TryGetValue(handle, out var texture))
1✔
369
        {
370
            texture.Width = width;
1✔
371
            texture.Height = height;
1✔
372
            texture.Format = format;
1✔
373
            texture.Data = data.ToArray();
1✔
374
            texture.MipLevels[level] = true;
1✔
375
        }
376
    }
1✔
377

378
    /// <inheritdoc />
379
    public void TexParameter(TextureTarget target, TextureParam param, int value)
380
    {
381
        if (BoundTextures.TryGetValue(ActiveTextureUnit, out var handle) && Textures.TryGetValue(handle, out var texture))
1✔
382
        {
383
            texture.Parameters[param] = value;
1✔
384
        }
385
    }
1✔
386

387
    /// <inheritdoc />
388
    public void TexSubImage2D(TextureTarget target, int level, int xOffset, int yOffset, int width, int height, PixelFormat format, ReadOnlySpan<byte> data)
389
    {
390
        // Track that a subimage update occurred
391
        if (BoundTextures.TryGetValue(ActiveTextureUnit, out var handle) && Textures.TryGetValue(handle, out var texture))
1✔
392
        {
393
            texture.SubImageUpdateCount++;
1✔
394
        }
395
    }
1✔
396

397
    /// <inheritdoc />
398
    public void GenerateMipmap(TextureTarget target)
399
    {
400
        if (BoundTextures.TryGetValue(ActiveTextureUnit, out var handle) && Textures.TryGetValue(handle, out var texture))
1✔
401
        {
402
            texture.HasGeneratedMipmaps = true;
1✔
403
        }
404
    }
1✔
405

406
    /// <inheritdoc />
407
    public void CompressedTexImage2D(
408
        TextureTarget target,
409
        int level,
410
        int width,
411
        int height,
412
        CompressedTextureFormat format,
413
        ReadOnlySpan<byte> data)
414
    {
UNCOV
415
        if (ShouldFailTextureLoad)
×
416
        {
UNCOV
417
            SimulatedErrorCode = 1;
×
418
            return;
×
419
        }
420

421
        if (BoundTextures.TryGetValue(ActiveTextureUnit, out var handle) && Textures.TryGetValue(handle, out var texture))
×
422
        {
UNCOV
423
            texture.Width = width;
×
424
            texture.Height = height;
×
UNCOV
425
            texture.CompressedFormat = format;
×
UNCOV
426
            texture.Data = data.ToArray();
×
UNCOV
427
            texture.MipLevels[level] = true;
×
428
        }
UNCOV
429
    }
×
430

431
    /// <inheritdoc />
432
    public void DeleteTexture(uint texture)
433
    {
434
        Textures.Remove(texture);
1✔
435
        foreach (var unit in BoundTextures.Where(kv => kv.Value == texture).Select(kv => kv.Key).ToList())
1✔
436
        {
437
            BoundTextures.Remove(unit);
1✔
438
        }
439
    }
1✔
440

441
    /// <inheritdoc />
442
    public void ActiveTexture(TextureUnit unit)
443
    {
444
        ActiveTextureUnit = unit;
1✔
445
    }
1✔
446

447
    /// <inheritdoc />
448
    public void PixelStore(PixelStoreParameter param, int value)
449
    {
450
        RenderState.PixelStoreParameters[param] = value;
1✔
451
    }
1✔
452

453
    #endregion
454

455
    #region Shader Operations
456

457
    /// <inheritdoc />
458
    public uint CreateProgram()
459
    {
460
        var handle = nextHandle++;
1✔
461
        Programs[handle] = new MockProgram(handle);
1✔
462
        return handle;
1✔
463
    }
464

465
    /// <inheritdoc />
466
    public uint CreateShader(ShaderType type)
467
    {
468
        var handle = nextHandle++;
1✔
469
        Shaders[handle] = new MockShader(handle, type);
1✔
470
        return handle;
1✔
471
    }
472

473
    /// <inheritdoc />
474
    public void ShaderSource(uint shader, string source)
475
    {
476
        if (Shaders.TryGetValue(shader, out var s))
1✔
477
        {
478
            s.Source = source;
1✔
479
        }
480
    }
1✔
481

482
    /// <inheritdoc />
483
    public void CompileShader(uint shader)
484
    {
485
        if (Shaders.TryGetValue(shader, out var s))
1✔
486
        {
487
            s.IsCompiled = !ShouldFailShaderCompile;
1✔
488
        }
489
    }
1✔
490

491
    /// <inheritdoc />
492
    public bool GetShaderCompileStatus(uint shader)
493
    {
494
        return Shaders.TryGetValue(shader, out var s) && s.IsCompiled;
1✔
495
    }
496

497
    /// <inheritdoc />
498
    public string GetShaderInfoLog(uint shader)
499
    {
500
        if (ShouldFailShaderCompile)
1✔
501
        {
502
            return "Mock shader compilation failed (ShouldFailShaderCompile = true)";
1✔
503
        }
504

505
        return string.Empty;
1✔
506
    }
507

508
    /// <inheritdoc />
509
    public void AttachShader(uint program, uint shader)
510
    {
511
        if (Programs.TryGetValue(program, out var p))
1✔
512
        {
513
            p.AttachedShaders.Add(shader);
1✔
514
        }
515
    }
1✔
516

517
    /// <inheritdoc />
518
    public void DetachShader(uint program, uint shader)
519
    {
520
        if (Programs.TryGetValue(program, out var p))
1✔
521
        {
522
            p.AttachedShaders.Remove(shader);
1✔
523
        }
524
    }
1✔
525

526
    /// <inheritdoc />
527
    public void LinkProgram(uint program)
528
    {
529
        if (Programs.TryGetValue(program, out var p))
1✔
530
        {
531
            p.IsLinked = !ShouldFailProgramLink;
1✔
532
        }
533
    }
1✔
534

535
    /// <inheritdoc />
536
    public bool GetProgramLinkStatus(uint program)
537
    {
538
        return Programs.TryGetValue(program, out var p) && p.IsLinked;
1✔
539
    }
540

541
    /// <inheritdoc />
542
    public string GetProgramInfoLog(uint program)
543
    {
544
        if (ShouldFailProgramLink)
1✔
545
        {
546
            return "Mock program linking failed (ShouldFailProgramLink = true)";
1✔
547
        }
548

UNCOV
549
        return string.Empty;
×
550
    }
551

552
    /// <inheritdoc />
553
    public void DeleteShader(uint shader)
554
    {
555
        Shaders.Remove(shader);
1✔
556
    }
1✔
557

558
    /// <inheritdoc />
559
    public void DeleteProgram(uint program)
560
    {
561
        Programs.Remove(program);
1✔
562
        if (BoundProgram == program)
1✔
563
        {
564
            BoundProgram = null;
1✔
565
        }
566
    }
1✔
567

568
    /// <inheritdoc />
569
    public void UseProgram(uint program)
570
    {
571
        BoundProgram = program == 0 ? null : program;
1✔
572
    }
1✔
573

574
    /// <inheritdoc />
575
    public int GetUniformLocation(uint program, string name)
576
    {
577
        if (Programs.TryGetValue(program, out var p))
1✔
578
        {
579
            if (!p.UniformLocations.TryGetValue(name, out var location))
1✔
580
            {
581
                location = p.UniformLocations.Count;
1✔
582
                p.UniformLocations[name] = location;
1✔
583
            }
584

585
            return location;
1✔
586
        }
587

588
        return -1;
1✔
589
    }
590

591
    /// <inheritdoc />
592
    public void Uniform1(int location, float value)
593
    {
594
        RecordUniform(location, value);
1✔
595
    }
1✔
596

597
    /// <inheritdoc />
598
    public void Uniform1(int location, int value)
599
    {
600
        RecordUniform(location, value);
1✔
601
    }
1✔
602

603
    /// <inheritdoc />
604
    public void Uniform2(int location, float x, float y)
605
    {
606
        RecordUniform(location, new Vector2(x, y));
1✔
607
    }
1✔
608

609
    /// <inheritdoc />
610
    public void Uniform3(int location, float x, float y, float z)
611
    {
612
        RecordUniform(location, new Vector3(x, y, z));
1✔
613
    }
1✔
614

615
    /// <inheritdoc />
616
    public void Uniform4(int location, float x, float y, float z, float w)
617
    {
618
        RecordUniform(location, new Vector4(x, y, z, w));
1✔
619
    }
1✔
620

621
    /// <inheritdoc />
622
    public void UniformMatrix4(int location, in Matrix4x4 matrix)
623
    {
624
        RecordUniform(location, matrix);
1✔
625
    }
1✔
626

627
    private void RecordUniform(int location, object value)
628
    {
629
        if (BoundProgram.HasValue && Programs.TryGetValue(BoundProgram.Value, out var p))
1✔
630
        {
631
            p.UniformValues[location] = value;
1✔
632
        }
633
    }
1✔
634

635
    #endregion
636

637
    #region Rendering Operations
638

639
    /// <inheritdoc />
640
    public void ClearColor(float r, float g, float b, float a)
641
    {
642
        RenderState.ClearColor = new Vector4(r, g, b, a);
1✔
643
    }
1✔
644

645
    /// <inheritdoc />
646
    public void Clear(ClearMask mask)
647
    {
648
        RenderState.LastClearMask = mask;
1✔
649
        RenderState.ClearCount++;
1✔
650
    }
1✔
651

652
    /// <inheritdoc />
653
    public void Enable(RenderCapability cap)
654
    {
655
        RenderState.EnabledCapabilities.Add(cap);
1✔
656
    }
1✔
657

658
    /// <inheritdoc />
659
    public void Disable(RenderCapability cap)
660
    {
661
        RenderState.EnabledCapabilities.Remove(cap);
1✔
662
    }
1✔
663

664
    /// <inheritdoc />
665
    public void CullFace(CullFaceMode mode)
666
    {
667
        RenderState.CullFaceMode = mode;
1✔
668
    }
1✔
669

670
    /// <inheritdoc />
671
    public void Viewport(int x, int y, uint width, uint height)
672
    {
673
        RenderState.Viewport = (x, y, (int)width, (int)height);
1✔
674
    }
1✔
675

676
    /// <inheritdoc />
677
    public void Scissor(int x, int y, uint width, uint height)
678
    {
679
        RenderState.ScissorRect = (x, y, (int)width, (int)height);
1✔
680
    }
1✔
681

682
    /// <inheritdoc />
683
    public void BlendFunc(BlendFactor srcFactor, BlendFactor dstFactor)
684
    {
685
        RenderState.BlendSrcFactor = srcFactor;
1✔
686
        RenderState.BlendDstFactor = dstFactor;
1✔
687
    }
1✔
688

689
    /// <inheritdoc />
690
    public void DepthFunc(DepthFunction func)
691
    {
692
        RenderState.DepthFunction = func;
1✔
693
    }
1✔
694

695
    /// <inheritdoc />
696
    public void DrawElements(PrimitiveType mode, uint count, IndexType type)
697
    {
698
        DrawCalls.Add(new DrawCall(
1✔
699
            mode,
1✔
700
            (int)count,
1✔
701
            IsIndexed: true,
1✔
702
            BoundProgram,
1✔
703
            BoundVAO,
1✔
704
            BoundTextures.Values.ToList(),
1✔
705
            InstanceCount: 1));
1✔
706
    }
1✔
707

708
    /// <inheritdoc />
709
    public void DrawArrays(PrimitiveType mode, int first, uint count)
710
    {
711
        DrawCalls.Add(new DrawCall(
1✔
712
            mode,
1✔
713
            (int)count,
1✔
714
            IsIndexed: false,
1✔
715
            BoundProgram,
1✔
716
            BoundVAO,
1✔
717
            BoundTextures.Values.ToList(),
1✔
718
            InstanceCount: 1));
1✔
719
    }
1✔
720

721
    /// <inheritdoc />
722
    public void DrawElementsInstanced(PrimitiveType mode, uint count, IndexType type, uint instanceCount)
723
    {
724
        DrawCalls.Add(new DrawCall(
×
725
            mode,
×
726
            (int)count,
×
727
            IsIndexed: true,
×
UNCOV
728
            BoundProgram,
×
UNCOV
729
            BoundVAO,
×
UNCOV
730
            BoundTextures.Values.ToList(),
×
UNCOV
731
            instanceCount));
×
732
    }
×
733

734
    /// <inheritdoc />
735
    public void DrawArraysInstanced(PrimitiveType mode, int first, uint count, uint instanceCount)
736
    {
737
        DrawCalls.Add(new DrawCall(
×
738
            mode,
×
739
            (int)count,
×
740
            IsIndexed: false,
×
UNCOV
741
            BoundProgram,
×
UNCOV
742
            BoundVAO,
×
UNCOV
743
            BoundTextures.Values.ToList(),
×
UNCOV
744
            instanceCount));
×
UNCOV
745
    }
×
746

747
    /// <inheritdoc />
748
    public void LineWidth(float width)
749
    {
750
        RenderState.LineWidth = width;
1✔
751
    }
1✔
752

753
    /// <inheritdoc />
754
    public void PointSize(float size)
755
    {
756
        RenderState.PointSize = size;
1✔
757
    }
1✔
758

759
    #endregion
760

761
    #region Error Handling
762

763
    /// <inheritdoc />
764
    public int GetError()
765
    {
766
        var error = SimulatedErrorCode;
1✔
767
        SimulatedErrorCode = 0;
1✔
768
        return error;
1✔
769
    }
770

771
    #endregion
772

773
    #region Framebuffer Operations
774

775
    /// <summary>
776
    /// Gets the dictionary of created framebuffers by handle.
777
    /// </summary>
778
    public Dictionary<uint, MockFramebuffer> Framebuffers { get; } = [];
2✔
779

780
    /// <summary>
781
    /// Gets the dictionary of created renderbuffers by handle.
782
    /// </summary>
783
    public Dictionary<uint, MockRenderbuffer> Renderbuffers { get; } = [];
2✔
784

785
    /// <summary>
786
    /// Gets or sets the currently bound framebuffer.
787
    /// </summary>
788
    public uint? BoundFramebuffer { get; private set; }
1✔
789

790
    /// <summary>
791
    /// Gets or sets the currently bound renderbuffer.
792
    /// </summary>
793
    public uint? BoundRenderbuffer { get; private set; }
1✔
794

795
    /// <summary>
796
    /// Gets or sets the simulated framebuffer status.
797
    /// </summary>
798
    public FramebufferStatus SimulatedFramebufferStatus { get; set; } = FramebufferStatus.Complete;
1✔
799

800
    /// <inheritdoc />
801
    public uint GenFramebuffer()
802
    {
UNCOV
803
        var handle = nextHandle++;
×
UNCOV
804
        Framebuffers[handle] = new MockFramebuffer(handle);
×
UNCOV
805
        return handle;
×
806
    }
807

808
    /// <inheritdoc />
809
    public void BindFramebuffer(FramebufferTarget target, uint framebuffer)
810
    {
UNCOV
811
        BoundFramebuffer = framebuffer == 0 ? null : framebuffer;
×
UNCOV
812
    }
×
813

814
    /// <inheritdoc />
815
    public void DeleteFramebuffer(uint framebuffer)
816
    {
UNCOV
817
        Framebuffers.Remove(framebuffer);
×
UNCOV
818
        if (BoundFramebuffer == framebuffer)
×
819
        {
UNCOV
820
            BoundFramebuffer = null;
×
821
        }
UNCOV
822
    }
×
823

824
    /// <inheritdoc />
825
    public void FramebufferTexture2D(FramebufferTarget target, FramebufferAttachment attachment, TextureTarget texTarget, uint texture, int level)
826
    {
UNCOV
827
        if (BoundFramebuffer.HasValue && Framebuffers.TryGetValue(BoundFramebuffer.Value, out var fbo))
×
828
        {
UNCOV
829
            fbo.Attachments[attachment] = new FramebufferTextureAttachment(texture, texTarget, level);
×
830
        }
UNCOV
831
    }
×
832

833
    /// <inheritdoc />
834
    public FramebufferStatus CheckFramebufferStatus(FramebufferTarget target)
835
    {
UNCOV
836
        return SimulatedFramebufferStatus;
×
837
    }
838

839
    /// <inheritdoc />
840
    public uint GenRenderbuffer()
841
    {
UNCOV
842
        var handle = nextHandle++;
×
UNCOV
843
        Renderbuffers[handle] = new MockRenderbuffer(handle);
×
UNCOV
844
        return handle;
×
845
    }
846

847
    /// <inheritdoc />
848
    public void BindRenderbuffer(uint renderbuffer)
849
    {
UNCOV
850
        BoundRenderbuffer = renderbuffer == 0 ? null : renderbuffer;
×
UNCOV
851
    }
×
852

853
    /// <inheritdoc />
854
    public void DeleteRenderbuffer(uint renderbuffer)
855
    {
UNCOV
856
        Renderbuffers.Remove(renderbuffer);
×
UNCOV
857
        if (BoundRenderbuffer == renderbuffer)
×
858
        {
UNCOV
859
            BoundRenderbuffer = null;
×
860
        }
UNCOV
861
    }
×
862

863
    /// <inheritdoc />
864
    public void RenderbufferStorage(RenderbufferFormat format, uint width, uint height)
865
    {
866
        if (BoundRenderbuffer.HasValue && Renderbuffers.TryGetValue(BoundRenderbuffer.Value, out var rbo))
×
867
        {
UNCOV
868
            rbo.Format = format;
×
UNCOV
869
            rbo.Width = (int)width;
×
UNCOV
870
            rbo.Height = (int)height;
×
871
        }
UNCOV
872
    }
×
873

874
    /// <inheritdoc />
875
    public void FramebufferRenderbuffer(FramebufferTarget target, FramebufferAttachment attachment, uint renderbuffer)
876
    {
UNCOV
877
        if (BoundFramebuffer.HasValue && Framebuffers.TryGetValue(BoundFramebuffer.Value, out var fbo))
×
878
        {
UNCOV
879
            fbo.RenderbufferAttachments[attachment] = renderbuffer;
×
880
        }
UNCOV
881
    }
×
882

883
    /// <inheritdoc />
884
    public void DrawBuffer(DrawBufferMode mode)
885
    {
UNCOV
886
        RenderState.DrawBufferMode = mode;
×
UNCOV
887
    }
×
888

889
    /// <inheritdoc />
890
    public void ReadBuffer(DrawBufferMode mode)
891
    {
UNCOV
892
        RenderState.ReadBufferMode = mode;
×
UNCOV
893
    }
×
894

895
    /// <inheritdoc />
896
    public void DepthMask(bool flag)
897
    {
UNCOV
898
        RenderState.DepthMaskEnabled = flag;
×
UNCOV
899
    }
×
900

901
    /// <inheritdoc />
902
    public void ColorMask(bool red, bool green, bool blue, bool alpha)
903
    {
UNCOV
904
        RenderState.ColorMask = (red, green, blue, alpha);
×
UNCOV
905
    }
×
906

907
    #endregion
908

909
    #region Debug
910

911
    /// <inheritdoc />
912
    public void GetTexImage(TextureTarget target, int level, PixelFormat format, Span<byte> data)
913
    {
914
        if (BoundTextures.TryGetValue(ActiveTextureUnit, out var handle) &&
1✔
915
            Textures.TryGetValue(handle, out var texture) &&
1✔
916
            texture.Data is not null)
1✔
917
        {
918
            var copyLength = Math.Min(data.Length, texture.Data.Length);
1✔
919
            texture.Data.AsSpan(0, copyLength).CopyTo(data);
1✔
920
        }
921
    }
1✔
922

923
    /// <inheritdoc />
924
    public void ReadFramebuffer(int x, int y, int width, int height, PixelFormat format, Span<byte> data)
925
    {
926
        if (SimulatedFramebufferData is null)
1✔
927
        {
UNCOV
928
            return;
×
929
        }
930

931
        // Calculate bytes per pixel based on format
932
        var bytesPerPixel = format switch
1✔
933
        {
1✔
UNCOV
934
            PixelFormat.R => 1,
×
UNCOV
935
            PixelFormat.RG => 2,
×
UNCOV
936
            PixelFormat.RGB => 3,
×
937
            PixelFormat.RGBA => 4,
1✔
UNCOV
938
            _ => 4
×
939
        };
1✔
940

941
        // If the request is for the full framebuffer and dimensions match, just copy
942
        if (x == 0 && y == 0 &&
1✔
943
            width == SimulatedFramebufferWidth &&
1✔
944
            height == SimulatedFramebufferHeight)
1✔
945
        {
946
            var copyLength = Math.Min(data.Length, SimulatedFramebufferData.Length);
1✔
947
            SimulatedFramebufferData.AsSpan(0, copyLength).CopyTo(data);
1✔
948
            return;
1✔
949
        }
950

951
        // Otherwise, copy the requested region
952
        var srcRowStride = SimulatedFramebufferWidth * bytesPerPixel;
1✔
953
        var dstRowStride = width * bytesPerPixel;
1✔
954

955
        for (var row = 0; row < height; row++)
1✔
956
        {
957
            var srcY = y + row;
1✔
958
            if (srcY < 0 || srcY >= SimulatedFramebufferHeight)
1✔
959
            {
960
                continue;
961
            }
962

963
            var srcOffset = (srcY * srcRowStride) + (x * bytesPerPixel);
1✔
964
            var dstOffset = row * dstRowStride;
1✔
965
            var rowBytes = Math.Min(dstRowStride, srcRowStride - (x * bytesPerPixel));
1✔
966

967
            if (srcOffset >= 0 && srcOffset + rowBytes <= SimulatedFramebufferData.Length &&
1✔
968
                dstOffset + rowBytes <= data.Length)
1✔
969
            {
970
                SimulatedFramebufferData.AsSpan(srcOffset, rowBytes).CopyTo(data.Slice(dstOffset, rowBytes));
1✔
971
            }
972
        }
973
    }
1✔
974

975
    #endregion
976

977
    /// <inheritdoc />
978
    public void Dispose()
979
    {
980
        if (!disposed)
1✔
981
        {
982
            disposed = true;
1✔
983
            Reset();
1✔
984
        }
985
    }
1✔
986
}
987

988
#region Supporting Types
989

990
/// <summary>
991
/// Represents a recorded draw call.
992
/// </summary>
993
/// <param name="PrimitiveType">The primitive type drawn.</param>
994
/// <param name="VertexCount">The number of vertices/indices.</param>
995
/// <param name="IsIndexed">Whether this was an indexed draw call.</param>
996
/// <param name="Program">The shader program used, if any.</param>
997
/// <param name="VAO">The VAO used, if any.</param>
998
/// <param name="Textures">The textures bound during the draw.</param>
999
/// <param name="InstanceCount">The number of instances drawn (1 for non-instanced).</param>
1000
public sealed record DrawCall(
1✔
1001
    PrimitiveType PrimitiveType,
1✔
1002
    int VertexCount,
1✔
1003
    bool IsIndexed,
1✔
1004
    uint? Program,
1✔
1005
    uint? VAO,
1✔
UNCOV
1006
    List<uint> Textures,
×
UNCOV
1007
    uint InstanceCount = 1);
×
1008

1009
/// <summary>
1010
/// Tracks buffer state.
1011
/// </summary>
1012
public sealed class MockBuffer(uint handle)
1✔
1013
{
1014
    /// <summary>
1015
    /// Gets the buffer handle.
1016
    /// </summary>
1017
    public uint Handle { get; } = handle;
1✔
1018

1019
    /// <summary>
1020
    /// Gets or sets the buffer data.
1021
    /// </summary>
1022
    public byte[]? Data { get; set; }
1✔
1023

1024
    /// <summary>
1025
    /// Gets or sets the buffer usage hint.
1026
    /// </summary>
1027
    public BufferUsage Usage { get; set; }
1✔
1028

1029
    /// <summary>
1030
    /// Gets or sets the buffer target.
1031
    /// </summary>
1032
    public BufferTarget Target { get; set; }
1✔
1033
}
1034

1035
/// <summary>
1036
/// Tracks texture state.
1037
/// </summary>
1038
public sealed class MockTexture(uint handle)
1✔
1039
{
1040
    /// <summary>
1041
    /// Gets the texture handle.
1042
    /// </summary>
1043
    public uint Handle { get; } = handle;
1✔
1044

1045
    /// <summary>
1046
    /// Gets or sets the texture width.
1047
    /// </summary>
1048
    public int Width { get; set; }
1✔
1049

1050
    /// <summary>
1051
    /// Gets or sets the texture height.
1052
    /// </summary>
1053
    public int Height { get; set; }
1✔
1054

1055
    /// <summary>
1056
    /// Gets or sets the pixel format.
1057
    /// </summary>
1058
    public PixelFormat Format { get; set; }
1✔
1059

1060
    /// <summary>
1061
    /// Gets or sets the compressed texture format, if this is a compressed texture.
1062
    /// </summary>
UNCOV
1063
    public CompressedTextureFormat? CompressedFormat { get; set; }
×
1064

1065
    /// <summary>
1066
    /// Gets or sets the texture data.
1067
    /// </summary>
1068
    public byte[]? Data { get; set; }
1✔
1069

1070
    /// <summary>
1071
    /// Gets or sets the texture target.
1072
    /// </summary>
1073
    public TextureTarget Target { get; set; }
1✔
1074

1075
    /// <summary>
1076
    /// Gets the texture parameters.
1077
    /// </summary>
1078
    public Dictionary<TextureParam, int> Parameters { get; } = [];
1✔
1079

1080
    /// <summary>
1081
    /// Gets the mip levels that have been set.
1082
    /// </summary>
1083
    public Dictionary<int, bool> MipLevels { get; } = [];
1✔
1084

1085
    /// <summary>
1086
    /// Gets or sets whether mipmaps have been generated.
1087
    /// </summary>
1088
    public bool HasGeneratedMipmaps { get; set; }
1✔
1089

1090
    /// <summary>
1091
    /// Gets or sets the count of sub-image updates.
1092
    /// </summary>
1093
    public int SubImageUpdateCount { get; set; }
1✔
1094
}
1095

1096
/// <summary>
1097
/// Tracks shader state.
1098
/// </summary>
1099
public sealed class MockShader(uint handle, ShaderType type)
1✔
1100
{
1101
    /// <summary>
1102
    /// Gets the shader handle.
1103
    /// </summary>
1104
    public uint Handle { get; } = handle;
1✔
1105

1106
    /// <summary>
1107
    /// Gets the shader type.
1108
    /// </summary>
1109
    public ShaderType Type { get; } = type;
1✔
1110

1111
    /// <summary>
1112
    /// Gets or sets the shader source code.
1113
    /// </summary>
1114
    public string? Source { get; set; }
1✔
1115

1116
    /// <summary>
1117
    /// Gets or sets whether the shader was compiled successfully.
1118
    /// </summary>
1119
    public bool IsCompiled { get; set; }
1✔
1120
}
1121

1122
/// <summary>
1123
/// Tracks shader program state.
1124
/// </summary>
1125
public sealed class MockProgram(uint handle)
1✔
1126
{
1127
    /// <summary>
1128
    /// Gets the program handle.
1129
    /// </summary>
1130
    public uint Handle { get; } = handle;
1✔
1131

1132
    /// <summary>
1133
    /// Gets the attached shader handles.
1134
    /// </summary>
1135
    public HashSet<uint> AttachedShaders { get; } = [];
1✔
1136

1137
    /// <summary>
1138
    /// Gets or sets whether the program was linked successfully.
1139
    /// </summary>
1140
    public bool IsLinked { get; set; }
1✔
1141

1142
    /// <summary>
1143
    /// Gets the uniform locations by name.
1144
    /// </summary>
1145
    public Dictionary<string, int> UniformLocations { get; } = [];
1✔
1146

1147
    /// <summary>
1148
    /// Gets the uniform values by location.
1149
    /// </summary>
1150
    public Dictionary<int, object> UniformValues { get; } = [];
1✔
1151
}
1152

1153
/// <summary>
1154
/// Tracks VAO state.
1155
/// </summary>
1156
public sealed class MockVao(uint handle)
1✔
1157
{
1158
    /// <summary>
1159
    /// Gets the VAO handle.
1160
    /// </summary>
1161
    public uint Handle { get; } = handle;
1✔
1162

1163
    /// <summary>
1164
    /// Gets the enabled vertex attributes.
1165
    /// </summary>
1166
    public HashSet<uint> EnabledAttributes { get; } = [];
1✔
1167

1168
    /// <summary>
1169
    /// Gets the vertex attribute configurations.
1170
    /// </summary>
1171
    public Dictionary<uint, VertexAttribute> Attributes { get; } = [];
1✔
1172

1173
    /// <summary>
1174
    /// Gets the vertex attribute divisors for instanced rendering.
1175
    /// </summary>
1176
    /// <remarks>
1177
    /// A divisor of 0 means per-vertex data (default).
1178
    /// A divisor of 1 means per-instance data.
1179
    /// A divisor of N means the attribute advances every N instances.
1180
    /// </remarks>
1181
    public Dictionary<uint, uint> AttributeDivisors { get; } = [];
1✔
1182
}
1183

1184
/// <summary>
1185
/// Represents a vertex attribute configuration.
1186
/// </summary>
1187
/// <param name="Index">The attribute index.</param>
1188
/// <param name="Size">The number of components.</param>
1189
/// <param name="Type">The data type.</param>
1190
/// <param name="Normalized">Whether to normalize.</param>
1191
/// <param name="Stride">The stride in bytes.</param>
1192
/// <param name="Offset">The offset in bytes.</param>
1193
public sealed record VertexAttribute(
1✔
1194
    uint Index,
1✔
1195
    int Size,
1✔
1196
    VertexAttribType Type,
1✔
1197
    bool Normalized,
1✔
1198
    uint Stride,
1✔
1199
    nuint Offset);
1✔
1200

1201
/// <summary>
1202
/// Tracks render state.
1203
/// </summary>
1204
public sealed class MockRenderState
1205
{
1206
    /// <summary>
1207
    /// Gets or sets the clear color.
1208
    /// </summary>
1209
    public Vector4 ClearColor { get; set; }
1✔
1210

1211
    /// <summary>
1212
    /// Gets or sets the last clear mask used.
1213
    /// </summary>
1214
    public ClearMask LastClearMask { get; set; }
1✔
1215

1216
    /// <summary>
1217
    /// Gets the number of clear operations.
1218
    /// </summary>
1219
    public int ClearCount { get; set; }
1✔
1220

1221
    /// <summary>
1222
    /// Gets the enabled render capabilities.
1223
    /// </summary>
1224
    public HashSet<RenderCapability> EnabledCapabilities { get; } = [];
2✔
1225

1226
    /// <summary>
1227
    /// Gets or sets the cull face mode.
1228
    /// </summary>
1229
    public CullFaceMode CullFaceMode { get; set; }
1✔
1230

1231
    /// <summary>
1232
    /// Gets or sets the viewport (x, y, width, height).
1233
    /// </summary>
1234
    public (int X, int Y, int Width, int Height) Viewport { get; set; }
1✔
1235

1236
    /// <summary>
1237
    /// Gets or sets the scissor rectangle (x, y, width, height).
1238
    /// </summary>
1239
    public (int X, int Y, int Width, int Height) ScissorRect { get; set; }
1✔
1240

1241
    /// <summary>
1242
    /// Gets or sets the source blend factor.
1243
    /// </summary>
1244
    public BlendFactor BlendSrcFactor { get; set; }
1✔
1245

1246
    /// <summary>
1247
    /// Gets or sets the destination blend factor.
1248
    /// </summary>
1249
    public BlendFactor BlendDstFactor { get; set; }
1✔
1250

1251
    /// <summary>
1252
    /// Gets or sets the depth function.
1253
    /// </summary>
1254
    public DepthFunction DepthFunction { get; set; }
1✔
1255

1256
    /// <summary>
1257
    /// Gets or sets the line width.
1258
    /// </summary>
1259
    public float LineWidth { get; set; } = 1f;
2✔
1260

1261
    /// <summary>
1262
    /// Gets or sets the point size.
1263
    /// </summary>
1264
    public float PointSize { get; set; } = 1f;
2✔
1265

1266
    /// <summary>
1267
    /// Gets the pixel store parameters.
1268
    /// </summary>
1269
    public Dictionary<PixelStoreParameter, int> PixelStoreParameters { get; } = [];
2✔
1270

1271
    /// <summary>
1272
    /// Gets or sets the draw buffer mode.
1273
    /// </summary>
1274
    public DrawBufferMode DrawBufferMode { get; set; }
1✔
1275

1276
    /// <summary>
1277
    /// Gets or sets the read buffer mode.
1278
    /// </summary>
1279
    public DrawBufferMode ReadBufferMode { get; set; }
1✔
1280

1281
    /// <summary>
1282
    /// Gets or sets whether depth writes are enabled.
1283
    /// </summary>
1284
    public bool DepthMaskEnabled { get; set; } = true;
2✔
1285

1286
    /// <summary>
1287
    /// Gets or sets the color mask (red, green, blue, alpha).
1288
    /// </summary>
1289
    public (bool Red, bool Green, bool Blue, bool Alpha) ColorMask { get; set; } = (true, true, true, true);
2✔
1290

1291
    /// <summary>
1292
    /// Resets the render state to defaults.
1293
    /// </summary>
1294
    public void Reset()
1295
    {
1296
        ClearColor = Vector4.Zero;
1✔
1297
        LastClearMask = default;
1✔
1298
        ClearCount = 0;
1✔
1299
        EnabledCapabilities.Clear();
1✔
1300
        CullFaceMode = default;
1✔
1301
        Viewport = default;
1✔
1302
        ScissorRect = default;
1✔
1303
        BlendSrcFactor = default;
1✔
1304
        BlendDstFactor = default;
1✔
1305
        DepthFunction = default;
1✔
1306
        LineWidth = 1f;
1✔
1307
        PointSize = 1f;
1✔
1308
        PixelStoreParameters.Clear();
1✔
1309
        DrawBufferMode = default;
1✔
1310
        ReadBufferMode = default;
1✔
1311
        DepthMaskEnabled = true;
1✔
1312
        ColorMask = (true, true, true, true);
1✔
1313
    }
1✔
1314
}
1315

1316
/// <summary>
1317
/// Tracks framebuffer state.
1318
/// </summary>
UNCOV
1319
public sealed class MockFramebuffer(uint handle)
×
1320
{
1321
    /// <summary>
1322
    /// Gets the framebuffer handle.
1323
    /// </summary>
UNCOV
1324
    public uint Handle { get; } = handle;
×
1325

1326
    /// <summary>
1327
    /// Gets the texture attachments by attachment point.
1328
    /// </summary>
UNCOV
1329
    public Dictionary<FramebufferAttachment, FramebufferTextureAttachment> Attachments { get; } = [];
×
1330

1331
    /// <summary>
1332
    /// Gets the renderbuffer attachments by attachment point.
1333
    /// </summary>
UNCOV
1334
    public Dictionary<FramebufferAttachment, uint> RenderbufferAttachments { get; } = [];
×
1335
}
1336

1337
/// <summary>
1338
/// Represents a texture attached to a framebuffer.
1339
/// </summary>
1340
/// <param name="Texture">The texture handle.</param>
1341
/// <param name="Target">The texture target.</param>
1342
/// <param name="Level">The mip level.</param>
UNCOV
1343
public sealed record FramebufferTextureAttachment(uint Texture, TextureTarget Target, int Level);
×
1344

1345
/// <summary>
1346
/// Tracks renderbuffer state.
1347
/// </summary>
UNCOV
1348
public sealed class MockRenderbuffer(uint handle)
×
1349
{
1350
    /// <summary>
1351
    /// Gets the renderbuffer handle.
1352
    /// </summary>
UNCOV
1353
    public uint Handle { get; } = handle;
×
1354

1355
    /// <summary>
1356
    /// Gets or sets the renderbuffer format.
1357
    /// </summary>
UNCOV
1358
    public RenderbufferFormat Format { get; set; }
×
1359

1360
    /// <summary>
1361
    /// Gets or sets the width.
1362
    /// </summary>
UNCOV
1363
    public int Width { get; set; }
×
1364

1365
    /// <summary>
1366
    /// Gets or sets the height.
1367
    /// </summary>
UNCOV
1368
    public int Height { get; set; }
×
1369
}
1370

1371
#endregion
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc