1+ /*
2+ * COPYRIGHT: See COPYING in the top level directory
3+ * PROJECT: RenderEngine;
4+ * FILE: Batched2DRenderer.cs
5+ * PURPOSE: Your file purpose here
6+ * PROGRAMMER: Peter Geinitz (Wayfarer)
7+ */
8+
9+ using System ;
10+ using System . Collections . Generic ;
11+ using System . Drawing ;
12+ using OpenTK . Graphics . OpenGL4 ;
13+
14+ namespace RenderEngine ;
15+
16+ public sealed class Batched2DRenderer : IDisposable
17+ {
18+ private readonly int _width ;
19+ private readonly int _height ;
20+
21+ private int _vboSolid ;
22+ private int _vaoSolid ;
23+ private readonly List < Vertex > _solidVertices = new ( ) ;
24+
25+ private int _vboTex ;
26+ private int _vaoTex ;
27+ private readonly List < Vertex > _texVertices = new ( ) ;
28+
29+ private int _solidShader ;
30+ private int _textureShader ;
31+
32+ private bool _initialized ;
33+
34+ private struct Vertex
35+ {
36+ public float X , Y ;
37+ public float R , G , B , A ;
38+ public float U , V ;
39+ public float TexIndex ;
40+ }
41+
42+ public Batched2DRenderer ( int width , int height )
43+ {
44+ _width = width ;
45+ _height = height ;
46+
47+ Test_DrawFullscreenQuad ( width , height ) ;
48+ }
49+
50+ private void EnsureInitialized ( )
51+ {
52+ if ( _initialized ) return ;
53+
54+ // --- Solid VAO/VBO ---
55+ _vaoSolid = GL . GenVertexArray ( ) ;
56+ _vboSolid = GL . GenBuffer ( ) ;
57+ GL . BindVertexArray ( _vaoSolid ) ;
58+ GL . BindBuffer ( BufferTarget . ArrayBuffer , _vboSolid ) ;
59+ GL . BufferData ( BufferTarget . ArrayBuffer , 1024 * 1024 , IntPtr . Zero , BufferUsageHint . DynamicDraw ) ;
60+ var strideSolid = 6 * sizeof ( float ) ;
61+ GL . VertexAttribPointer ( 0 , 2 , VertexAttribPointerType . Float , false , strideSolid , 0 ) ;
62+ GL . EnableVertexAttribArray ( 0 ) ;
63+ GL . VertexAttribPointer ( 1 , 4 , VertexAttribPointerType . Float , false , strideSolid , 2 * sizeof ( float ) ) ;
64+ GL . EnableVertexAttribArray ( 1 ) ;
65+ GL . BindVertexArray ( 0 ) ;
66+
67+ // --- Textured VAO/VBO ---
68+ _vaoTex = GL . GenVertexArray ( ) ;
69+ _vboTex = GL . GenBuffer ( ) ;
70+ GL . BindVertexArray ( _vaoTex ) ;
71+ GL . BindBuffer ( BufferTarget . ArrayBuffer , _vboTex ) ;
72+ GL . BufferData ( BufferTarget . ArrayBuffer , 1024 * 1024 , IntPtr . Zero , BufferUsageHint . DynamicDraw ) ;
73+ var strideTex = 9 * sizeof ( float ) ;
74+ GL . VertexAttribPointer ( 0 , 2 , VertexAttribPointerType . Float , false , strideTex , 0 ) ;
75+ GL . EnableVertexAttribArray ( 0 ) ;
76+ GL . VertexAttribPointer ( 1 , 4 , VertexAttribPointerType . Float , false , strideTex , 2 * sizeof ( float ) ) ;
77+ GL . EnableVertexAttribArray ( 1 ) ;
78+ GL . VertexAttribPointer ( 2 , 2 , VertexAttribPointerType . Float , false , strideTex , 6 * sizeof ( float ) ) ;
79+ GL . EnableVertexAttribArray ( 2 ) ;
80+ GL . VertexAttribPointer ( 3 , 1 , VertexAttribPointerType . Float , false , strideTex , 8 * sizeof ( float ) ) ;
81+ GL . EnableVertexAttribArray ( 3 ) ;
82+ GL . BindVertexArray ( 0 ) ;
83+
84+ // --- Shaders ---
85+ _solidShader = CompileShader ( SolidVertex ( ) , SolidFragment ( ) ) ;
86+ _textureShader = CompileShader ( TextureVertex ( ) , TextureFragment ( ) ) ;
87+
88+ _initialized = true ;
89+ }
90+
91+ // Minimal test — renders a fullscreen red quad using its own VAO/VBO/shader (no batching)
92+ void Test_DrawFullscreenQuad ( int width , int height )
93+ {
94+ // create simple shader program (compile once; re-create each test is fine)
95+ var vs = @"
96+ #version 410 core
97+ layout(location = 0) in vec2 aPos;
98+ void main() {
99+ // positions are already in clip space
100+ gl_Position = vec4(aPos, 0.0, 1.0);
101+ }" ;
102+ var fs = @"
103+ #version 410 core
104+ out vec4 FragColor;
105+ void main() { FragColor = vec4(1.0, 0.0, 0.0, 1.0); }" ; // solid red
106+
107+ var v = GL . CreateShader ( ShaderType . VertexShader ) ;
108+ GL . ShaderSource ( v , vs ) ;
109+ GL . CompileShader ( v ) ;
110+ GL . GetShader ( v , ShaderParameter . CompileStatus , out var okv ) ;
111+ if ( okv == 0 ) throw new Exception ( "VS: " + GL . GetShaderInfoLog ( v ) ) ;
112+
113+ var f = GL . CreateShader ( ShaderType . FragmentShader ) ;
114+ GL . ShaderSource ( f , fs ) ;
115+ GL . CompileShader ( f ) ;
116+ GL . GetShader ( f , ShaderParameter . CompileStatus , out var okf ) ;
117+ if ( okf == 0 ) throw new Exception ( "FS: " + GL . GetShaderInfoLog ( f ) ) ;
118+
119+ var prog = GL . CreateProgram ( ) ;
120+ GL . AttachShader ( prog , v ) ;
121+ GL . AttachShader ( prog , f ) ;
122+ GL . LinkProgram ( prog ) ;
123+ GL . GetProgram ( prog , GetProgramParameterName . LinkStatus , out var linkOk ) ;
124+ if ( linkOk == 0 ) throw new Exception ( "Link: " + GL . GetProgramInfoLog ( prog ) ) ;
125+
126+ GL . DeleteShader ( v ) ;
127+ GL . DeleteShader ( f ) ;
128+
129+ // Fullscreen quad in clip space (already -1..1)
130+ var verts = new float [ ]
131+ {
132+ - 1f , 1f , // top-left
133+ 1f , 1f , // top-right
134+ 1f , - 1f , // bottom-right
135+
136+ 1f , - 1f ,
137+ - 1f , - 1f ,
138+ - 1f , 1f
139+ } ;
140+
141+ var vao = GL . GenVertexArray ( ) ;
142+ var vbo = GL . GenBuffer ( ) ;
143+
144+ GL . BindVertexArray ( vao ) ;
145+ GL . BindBuffer ( BufferTarget . ArrayBuffer , vbo ) ;
146+ GL . BufferData ( BufferTarget . ArrayBuffer , verts . Length * sizeof ( float ) , verts , BufferUsageHint . StaticDraw ) ;
147+ GL . VertexAttribPointer ( 0 , 2 , VertexAttribPointerType . Float , false , 2 * sizeof ( float ) , 0 ) ;
148+ GL . EnableVertexAttribArray ( 0 ) ;
149+
150+ // Render
151+ GL . Viewport ( 0 , 0 , width , height ) ;
152+ GL . Disable ( EnableCap . DepthTest ) ;
153+ GL . ClearColor ( Color . CornflowerBlue ) ;
154+ GL . Clear ( ClearBufferMask . ColorBufferBit ) ;
155+ GL . UseProgram ( prog ) ;
156+ GL . BindVertexArray ( vao ) ;
157+ GL . DrawArrays ( PrimitiveType . Triangles , 0 , 6 ) ;
158+
159+ // Read any GL error
160+ var err = GL . GetError ( ) ;
161+
162+ // cleanup
163+ GL . BindVertexArray ( 0 ) ;
164+ GL . DeleteBuffer ( vbo ) ;
165+ GL . DeleteVertexArray ( vao ) ;
166+ GL . DeleteProgram ( prog ) ;
167+
168+ // If err != NoError, capture it and log
169+ if ( err != ErrorCode . NoError ) throw new Exception ( "GL error: " + err . ToString ( ) ) ;
170+ }
171+
172+
173+ #region Shaders
174+
175+ private string SolidVertex ( ) => $@ "
176+ #version 410 core
177+ layout(location=0) in vec2 aPos;
178+ layout(location=1) in vec4 aColor;
179+ out vec4 vColor;
180+ void main() {{
181+ vec2 pos = aPos / vec2({ _width } ,{ _height } ) * 2.0 - 1.0;
182+ pos.y = -pos.y;
183+ gl_Position = vec4(pos,0,1);
184+ vColor = aColor;
185+ }}" ;
186+
187+ private string SolidFragment ( ) => @"
188+ #version 410 core
189+ in vec4 vColor;
190+ out vec4 FragColor;
191+ void main() { FragColor = vColor; }" ;
192+
193+ private string TextureVertex ( ) => $@ "
194+ #version 410 core
195+ layout(location=0) in vec2 aPos;
196+ layout(location=1) in vec4 aColor;
197+ layout(location=2) in vec2 aTex;
198+ layout(location=3) in float aTexIndex;
199+ out vec2 vTex;
200+ out vec4 vColor;
201+ out float vTexIndex;
202+ void main() {{
203+ vec2 pos = aPos / vec2({ _width } ,{ _height } ) * 2.0 - 1.0;
204+ pos.y = -pos.y;
205+ gl_Position = vec4(pos,0,1);
206+ vTex = aTex;
207+ vColor = aColor;
208+ vTexIndex = aTexIndex;
209+ }}" ;
210+
211+ private string TextureFragment ( ) => @"
212+ #version 410 core
213+ in vec2 vTex;
214+ in vec4 vColor;
215+ in float vTexIndex;
216+ uniform sampler2D uTexture;
217+ out vec4 FragColor;
218+ void main() {
219+ vec4 texColor = texture(uTexture, vTex);
220+ FragColor = texColor * vColor;
221+ }" ;
222+
223+ #endregion
224+
225+ private int CompileShader ( string vs , string fs )
226+ {
227+ var v = GL . CreateShader ( ShaderType . VertexShader ) ;
228+ GL . ShaderSource ( v , vs ) ;
229+ GL . CompileShader ( v ) ;
230+ GL . GetShader ( v , ShaderParameter . CompileStatus , out var success ) ;
231+ if ( success == 0 ) throw new Exception ( GL . GetShaderInfoLog ( v ) ) ;
232+
233+ var f = GL . CreateShader ( ShaderType . FragmentShader ) ;
234+ GL . ShaderSource ( f , fs ) ;
235+ GL . CompileShader ( f ) ;
236+ GL . GetShader ( f , ShaderParameter . CompileStatus , out success ) ;
237+ if ( success == 0 ) throw new Exception ( GL . GetShaderInfoLog ( f ) ) ;
238+
239+ var program = GL . CreateProgram ( ) ;
240+ GL . AttachShader ( program , v ) ;
241+ GL . AttachShader ( program , f ) ;
242+ GL . LinkProgram ( program ) ;
243+ GL . GetProgram ( program , GetProgramParameterName . LinkStatus , out success ) ;
244+ if ( success == 0 ) throw new Exception ( GL . GetProgramInfoLog ( program ) ) ;
245+
246+ GL . DeleteShader ( v ) ;
247+ GL . DeleteShader ( f ) ;
248+ return program ;
249+ }
250+
251+ private void AddSolidVertex ( float x , float y , float r , float g , float b , float a )
252+ => _solidVertices . Add ( new Vertex { X = x , Y = y , R = r , G = g , B = b , A = a } ) ;
253+
254+ private void AddTexVertex ( float x , float y , float r , float g , float b , float a , float u , float v , float texId )
255+ => _texVertices . Add ( new Vertex { X = x , Y = y , R = r , G = g , B = b , A = a , U = u , V = v , TexIndex = texId } ) ;
256+
257+ #region Public Draw API
258+
259+ public void DrawSolidQuad ( Point p0 , Point p1 , Point p2 , Point p3 , Color color )
260+ {
261+ EnsureInitialized ( ) ;
262+ var r = color . R / 255f ;
263+ var g = color . G / 255f ;
264+ var b = color . B / 255f ;
265+ var a = color . A / 255f ;
266+
267+ AddSolidVertex ( p0 . X , p0 . Y , r , g , b , a ) ;
268+ AddSolidVertex ( p1 . X , p1 . Y , r , g , b , a ) ;
269+ AddSolidVertex ( p2 . X , p2 . Y , r , g , b , a ) ;
270+
271+ AddSolidVertex ( p2 . X , p2 . Y , r , g , b , a ) ;
272+ AddSolidVertex ( p3 . X , p3 . Y , r , g , b , a ) ;
273+ AddSolidVertex ( p0 . X , p0 . Y , r , g , b , a ) ;
274+ }
275+
276+ public void DrawTexturedQuad ( Point p0 , Point p1 , Point p2 , Point p3 , int texId , float u0 = 0 , float v0 = 0 ,
277+ float u1 = 1 , float v1 = 1 )
278+ {
279+ EnsureInitialized ( ) ;
280+ AddTexVertex ( p0 . X , p0 . Y , 1 , 1 , 1 , 1 , u0 , v0 , texId ) ;
281+ AddTexVertex ( p1 . X , p1 . Y , 1 , 1 , 1 , 1 , u1 , v0 , texId ) ;
282+ AddTexVertex ( p2 . X , p2 . Y , 1 , 1 , 1 , 1 , u1 , v1 , texId ) ;
283+
284+ AddTexVertex ( p2 . X , p2 . Y , 1 , 1 , 1 , 1 , u1 , v1 , texId ) ;
285+ AddTexVertex ( p3 . X , p3 . Y , 1 , 1 , 1 , 1 , u0 , v1 , texId ) ;
286+ AddTexVertex ( p0 . X , p0 . Y , 1 , 1 , 1 , 1 , u0 , v0 , texId ) ;
287+ }
288+
289+ #endregion
290+
291+ public void Flush ( )
292+ {
293+ EnsureInitialized ( ) ;
294+
295+ // --- Solid ---
296+ if ( _solidVertices . Count > 0 )
297+ {
298+ GL . BindVertexArray ( _vaoSolid ) ;
299+ GL . BindBuffer ( BufferTarget . ArrayBuffer , _vboSolid ) ;
300+
301+ var raw = new float [ _solidVertices . Count * 6 ] ;
302+ for ( var i = 0 ; i < _solidVertices . Count ; i ++ )
303+ {
304+ var idx = i * 6 ;
305+ var v = _solidVertices [ i ] ;
306+ raw [ idx + 0 ] = v . X ;
307+ raw [ idx + 1 ] = v . Y ;
308+ raw [ idx + 2 ] = v . R ;
309+ raw [ idx + 3 ] = v . G ;
310+ raw [ idx + 4 ] = v . B ;
311+ raw [ idx + 5 ] = v . A ;
312+ }
313+
314+ GL . BufferSubData ( BufferTarget . ArrayBuffer , IntPtr . Zero , raw . Length * sizeof ( float ) , raw ) ;
315+
316+ GL . UseProgram ( _solidShader ) ;
317+ GL . DrawArrays ( PrimitiveType . Triangles , 0 , _solidVertices . Count ) ;
318+ _solidVertices . Clear ( ) ;
319+ }
320+
321+ // --- Textured ---
322+ if ( _texVertices . Count > 0 )
323+ {
324+ GL . BindVertexArray ( _vaoTex ) ;
325+ GL . BindBuffer ( BufferTarget . ArrayBuffer , _vboTex ) ;
326+
327+ var raw = new float [ _texVertices . Count * 9 ] ;
328+ for ( var i = 0 ; i < _texVertices . Count ; i ++ )
329+ {
330+ var idx = i * 9 ;
331+ var v = _texVertices [ i ] ;
332+ raw [ idx + 0 ] = v . X ;
333+ raw [ idx + 1 ] = v . Y ;
334+ raw [ idx + 2 ] = v . R ;
335+ raw [ idx + 3 ] = v . G ;
336+ raw [ idx + 4 ] = v . B ;
337+ raw [ idx + 5 ] = v . A ;
338+ raw [ idx + 6 ] = v . U ;
339+ raw [ idx + 7 ] = v . V ;
340+ raw [ idx + 8 ] = v . TexIndex ;
341+ }
342+
343+ GL . BufferSubData ( BufferTarget . ArrayBuffer , IntPtr . Zero , raw . Length * sizeof ( float ) , raw ) ;
344+
345+ GL . UseProgram ( _textureShader ) ;
346+ // Bind first texture only (for simplicity)
347+ var texId = ( int ) _texVertices [ 0 ] . TexIndex ;
348+ GL . ActiveTexture ( TextureUnit . Texture0 ) ;
349+ GL . BindTexture ( TextureTarget . Texture2D , texId ) ;
350+ var loc = GL . GetUniformLocation ( _textureShader , "uTexture" ) ;
351+ GL . Uniform1 ( loc , 0 ) ;
352+
353+ GL . DrawArrays ( PrimitiveType . Triangles , 0 , _texVertices . Count ) ;
354+ _texVertices . Clear ( ) ;
355+ }
356+
357+ GL . BindVertexArray ( 0 ) ;
358+ }
359+
360+ public void Dispose ( )
361+ {
362+ if ( ! _initialized ) return ;
363+
364+ GL . DeleteVertexArray ( _vaoSolid ) ;
365+ GL . DeleteBuffer ( _vboSolid ) ;
366+ GL . DeleteVertexArray ( _vaoTex ) ;
367+ GL . DeleteBuffer ( _vboTex ) ;
368+ GL . DeleteProgram ( _solidShader ) ;
369+ GL . DeleteProgram ( _textureShader ) ;
370+ _initialized = false ;
371+ }
372+ }
0 commit comments