glplot 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,250 @@
1
+ from __future__ import annotations
2
+ import ctypes as C
3
+ import numpy as np
4
+ from OpenGL.GL import *
5
+ from typing import TYPE_CHECKING, Optional, Tuple
6
+
7
+ from .base import GLLineBuffers
8
+ from ..utils.shaders import WIDE_LINES_INSTANCED_VS, EXACT_LINES_FS, DENSITY_ACCUM_FS
9
+ from ..utils.gl_utils import link_program
10
+
11
+ if TYPE_CHECKING:
12
+ from ..core.layers import LineFamilyLayer
13
+ from ..core.context import RenderContext
14
+ from ..options import EngineOptions
15
+
16
+ class LineFamilyRenderer:
17
+ """
18
+ Primitive renderer for LineFamilyLayer.
19
+ Specialized for high-performance instanced rendering of lines via quad expansion.
20
+ Uses optimized orthographic shaders for maximum throughput.
21
+ """
22
+ def __init__(self, options: EngineOptions):
23
+ self.options = options
24
+ self.prog = 0
25
+
26
+ # Uniform locations (Exact)
27
+ self.u_ndc_scale = -1
28
+ self.u_ndc_offset = -1
29
+ self.u_xrange = -1
30
+ self.u_window = -1
31
+ self.u_use_color = -1
32
+ self.u_alpha = -1
33
+ self.u_width = -1
34
+ self.u_viewport = -1
35
+ self.u_keep_prob = -1
36
+ self.u_total_count = -1
37
+ self.u_offset = -1
38
+ self.u_use_colormap = -1
39
+ self.u_scheme = -1
40
+
41
+ # Accumulation uniforms
42
+ self.accum_prog = 0
43
+ self.u_accum_ndc_scale = -1
44
+ self.u_accum_ndc_offset = -1
45
+ self.u_accum_xrange = -1
46
+ self.u_accum_window = -1
47
+ self.u_accum_use_color = -1
48
+ self.u_accum_alpha = -1
49
+ self.u_accum_width = -1
50
+ self.u_accum_viewport = -1
51
+ self.u_accum_keep_prob = -1
52
+ self.u_accum_offset = -1
53
+
54
+ # Configuration
55
+ self.use_fp16_ab = True
56
+ self.use_packed_color = True
57
+
58
+ def initialize(self) -> None:
59
+ """Link wide-line instanced shaders and setup uniform locations."""
60
+ self.prog = link_program(WIDE_LINES_INSTANCED_VS, EXACT_LINES_FS)
61
+ self.u_ndc_scale = glGetUniformLocation(self.prog, "u_ndc_scale")
62
+ self.u_ndc_offset = glGetUniformLocation(self.prog, "u_ndc_offset")
63
+ self.u_xrange = glGetUniformLocation(self.prog, "u_xrange")
64
+ self.u_window = glGetUniformLocation(self.prog, "u_window")
65
+ self.u_use_color = glGetUniformLocation(self.prog, "u_use_color")
66
+ self.u_alpha = glGetUniformLocation(self.prog, "u_alpha")
67
+ self.u_width = glGetUniformLocation(self.prog, "u_width")
68
+ self.u_viewport = glGetUniformLocation(self.prog, "u_viewport_size")
69
+ self.u_keep_prob = glGetUniformLocation(self.prog, "u_keep_prob")
70
+ self.u_total_count = glGetUniformLocation(self.prog, "u_total_count")
71
+ self.u_offset = glGetUniformLocation(self.prog, "u_layer_offset")
72
+ self.u_use_colormap = glGetUniformLocation(self.prog, "u_use_colormap")
73
+ self.u_scheme = glGetUniformLocation(self.prog, "u_scheme")
74
+
75
+ # Density Accumulation Program
76
+ self.accum_prog = link_program(WIDE_LINES_INSTANCED_VS, DENSITY_ACCUM_FS)
77
+ self.u_accum_ndc_scale = glGetUniformLocation(self.accum_prog, "u_ndc_scale")
78
+ self.u_accum_ndc_offset = glGetUniformLocation(self.accum_prog, "u_ndc_offset")
79
+ self.u_accum_xrange = glGetUniformLocation(self.accum_prog, "u_xrange")
80
+ self.u_accum_window = glGetUniformLocation(self.accum_prog, "u_window")
81
+ self.u_accum_use_color = glGetUniformLocation(self.accum_prog, "u_use_color")
82
+ self.u_accum_alpha = glGetUniformLocation(self.accum_prog, "u_alpha")
83
+ self.u_accum_width = glGetUniformLocation(self.accum_prog, "u_width")
84
+ self.u_accum_viewport = glGetUniformLocation(self.accum_prog, "u_viewport_size")
85
+ self.u_accum_keep_prob = glGetUniformLocation(self.accum_prog, "u_keep_prob")
86
+ self.u_accum_offset = glGetUniformLocation(self.accum_prog, "u_layer_offset")
87
+
88
+ def _create_buffers(self, layer: LineFamilyLayer) -> GLLineBuffers:
89
+ """Create and initialize GPU buffers for a quad-expanded line family."""
90
+ bufs = GLLineBuffers()
91
+ bufs.vao = glGenVertexArrays(1)
92
+ glBindVertexArray(bufs.vao)
93
+
94
+ # 1. Base vertex buffer (a_corner: [t, side] x 4)
95
+ # t in {0,1}, side in {-0.5, 0.5}
96
+ bufs.vbo_base = glGenBuffers(1)
97
+ glBindBuffer(GL_ARRAY_BUFFER, bufs.vbo_base)
98
+ quad_data = np.array([
99
+ 0.0, -0.5,
100
+ 0.0, 0.5,
101
+ 1.0, -0.5,
102
+ 1.0, 0.5
103
+ ], dtype=np.float32)
104
+ glBufferData(GL_ARRAY_BUFFER, quad_data.nbytes, quad_data, GL_STATIC_DRAW)
105
+
106
+ # a_corner (loc 0)
107
+ glEnableVertexAttribArray(0)
108
+ glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 8, C.c_void_p(0))
109
+
110
+ # 2. Instanced line parameters (a, b) (loc 1)
111
+ bufs.vbo_ab = glGenBuffers(1)
112
+ glBindBuffer(GL_ARRAY_BUFFER, bufs.vbo_ab)
113
+ glBufferData(GL_ARRAY_BUFFER, 16, None, GL_STATIC_DRAW)
114
+ glEnableVertexAttribArray(1)
115
+ if self.use_fp16_ab:
116
+ glVertexAttribPointer(1, 2, GL_HALF_FLOAT, GL_FALSE, 0, C.c_void_p(0))
117
+ else:
118
+ glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, C.c_void_p(0))
119
+ glVertexAttribDivisor(1, 1)
120
+
121
+ # 3. Instanced colors (loc 2)
122
+ bufs.vbo_col = glGenBuffers(1)
123
+ glBindBuffer(GL_ARRAY_BUFFER, bufs.vbo_col)
124
+ glBufferData(GL_ARRAY_BUFFER, 16, None, GL_STATIC_DRAW)
125
+ glEnableVertexAttribArray(2)
126
+ if self.use_packed_color:
127
+ glVertexAttribPointer(2, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, C.c_void_p(0))
128
+ else:
129
+ glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, 0, C.c_void_p(0))
130
+ glVertexAttribDivisor(2, 1)
131
+
132
+ glBindVertexArray(0)
133
+ return bufs
134
+
135
+ def update_gpu_data(self, layer: LineFamilyLayer, bufs: GLLineBuffers) -> None:
136
+ """Upload semantic data to GPU buffers."""
137
+ if layer.ab is None: return
138
+
139
+ glBindVertexArray(bufs.vao)
140
+
141
+ # Upload AB
142
+ ab_u = layer.ab.astype(np.float16) if self.use_fp16_ab else layer.ab
143
+ glBindBuffer(GL_ARRAY_BUFFER, bufs.vbo_ab)
144
+ glBufferData(GL_ARRAY_BUFFER, ab_u.nbytes, ab_u, GL_STATIC_DRAW)
145
+
146
+ # Upload Colors
147
+ bufs.has_color = layer.colors is not None
148
+ if bufs.has_color:
149
+ if self.use_packed_color:
150
+ cols_u8 = np.clip(layer.colors * 255.0, 0, 255).astype(np.uint8, copy=False)
151
+ glBindBuffer(GL_ARRAY_BUFFER, bufs.vbo_col)
152
+ glBufferData(GL_ARRAY_BUFFER, cols_u8.nbytes, cols_u8, GL_STATIC_DRAW)
153
+ else:
154
+ glBindBuffer(GL_ARRAY_BUFFER, bufs.vbo_col)
155
+ glBufferData(GL_ARRAY_BUFFER, layer.colors.nbytes, layer.colors, GL_STATIC_DRAW)
156
+
157
+ glBindVertexArray(0)
158
+ layer.dirty.gpu_dirty = False
159
+
160
+ def draw(self, layer: LineFamilyLayer, ctx: RenderContext) -> None:
161
+ """Draw the layer using current context."""
162
+ if layer.ab is None or len(layer.ab) == 0: return
163
+
164
+ # 1. Resource Management
165
+ if not hasattr(layer, "_gl") or layer._gl is None:
166
+ layer._gl = self._create_buffers(layer)
167
+ layer.dirty.gpu_dirty = True
168
+
169
+ if layer.dirty.gpu_dirty:
170
+ self.update_gpu_data(layer, layer._gl)
171
+
172
+ # 2. Setup Shaders & Uniforms
173
+ glUseProgram(self.prog)
174
+ glUniform2f(self.u_ndc_scale, *ctx.ndc_scale)
175
+ glUniform2f(self.u_ndc_offset, *ctx.ndc_offset)
176
+ glUniform2f(self.u_viewport, float(ctx.fb_width), float(ctx.fb_height))
177
+ glUniform2f(self.u_xrange, float(layer.x_range[0]), float(layer.x_range[1]))
178
+ glUniform4f(self.u_window, *ctx.window_world)
179
+ glUniform1i(self.u_use_color, 1 if layer._gl.has_color else 0)
180
+
181
+ # Style Application
182
+ overrides = self.options.visual.overrides
183
+ alpha = ctx.global_alpha * layer.style.alpha * overrides.alpha_multiplier
184
+ glUniform1f(self.u_alpha, float(alpha))
185
+
186
+ width = layer.style.line_width * overrides.line_width_multiplier
187
+ glUniform1f(self.u_width, float(width))
188
+
189
+ # LOD
190
+ prob = ctx.lod_keep_prob
191
+ glUniform1f(self.u_keep_prob, float(prob))
192
+ glUniform1i(self.u_total_count, len(layer.ab))
193
+ glUniform2f(self.u_offset, *layer.translation)
194
+
195
+ # Colormap
196
+ glUniform1i(self.u_use_colormap, 1 if self.options.line_colormap_enabled else 0)
197
+ glUniform1i(self.u_scheme, self.options.density_scheme_index)
198
+
199
+ # 3. Draw call (Instanced Quads)
200
+ glBindVertexArray(layer._gl.vao)
201
+ glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, len(layer.ab))
202
+
203
+ # Cleanup
204
+ glBindVertexArray(0)
205
+ glUseProgram(0)
206
+
207
+ def draw_density(self, layer: LineFamilyLayer, ctx: RenderContext) -> None:
208
+ """Accumulate line density into the current R32F target."""
209
+ if layer.ab is None or len(layer.ab) == 0: return
210
+
211
+ # 1. Resource Management
212
+ if not hasattr(layer, "_gl") or layer._gl is None:
213
+ layer._gl = self._create_buffers(layer)
214
+ layer.dirty.gpu_dirty = True
215
+
216
+ if layer.dirty.gpu_dirty:
217
+ self.update_gpu_data(layer, layer._gl)
218
+
219
+ # 2. Setup Shaders
220
+ glUseProgram(self.accum_prog)
221
+ glUniform2f(self.u_accum_ndc_scale, *ctx.ndc_scale)
222
+ glUniform2f(self.u_accum_ndc_offset, *ctx.ndc_offset)
223
+ glUniform2f(self.u_accum_viewport, float(ctx.fb_width), float(ctx.fb_height))
224
+ glUniform2f(self.u_accum_xrange, float(layer.x_range[0]), float(layer.x_range[1]))
225
+ glUniform4f(self.u_accum_window, *ctx.window_world)
226
+
227
+ overrides = self.options.visual.overrides
228
+ width = max(1.0, layer.style.line_width * overrides.line_width_multiplier)
229
+ glUniform1f(self.u_accum_width, float(width))
230
+
231
+ if self.options.density_weighted:
232
+ glUniform1i(self.u_accum_use_color, 1 if layer._gl.has_color else 0)
233
+ alpha = ctx.global_alpha * layer.style.alpha * overrides.alpha_multiplier
234
+ glUniform1f(self.u_accum_alpha, float(alpha))
235
+ else:
236
+ glUniform1i(self.u_accum_use_color, 0)
237
+ glUniform1f(self.u_accum_alpha, 1.0)
238
+
239
+ # LOD
240
+ prob = ctx.lod_keep_prob
241
+ glUniform1f(self.u_accum_keep_prob, float(prob))
242
+ glUniform2f(self.u_accum_offset, *layer.translation)
243
+
244
+ # 3. Draw call
245
+ glBindVertexArray(layer._gl.vao)
246
+ glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, len(layer.ab))
247
+
248
+ # Cleanup
249
+ glBindVertexArray(0)
250
+ glUseProgram(0)
@@ -0,0 +1,149 @@
1
+ from __future__ import annotations
2
+ import ctypes as C
3
+ import numpy as np
4
+ from OpenGL.GL import *
5
+ from typing import TYPE_CHECKING, Optional, Tuple
6
+
7
+ from ..utils.shaders import PATCH_VS, PATCH_FS, DENSITY_ACCUM_FS
8
+ from ..utils.gl_utils import link_program
9
+
10
+ if TYPE_CHECKING:
11
+ from ..core.layers import PatchLayer
12
+ from ..core.context import RenderContext
13
+ from ..options import EngineOptions
14
+
15
+ class GLPatchBuffers:
16
+ def __init__(self, vao: int, vbo: int, ebo: Optional[int] = None):
17
+ self.vao = vao
18
+ self.vbo = vbo
19
+ self.ebo = ebo
20
+ self.count = 0
21
+
22
+ class PatchRenderer:
23
+ """
24
+ Primitive renderer for PatchLayer.
25
+ Specialized for area fills, bars, and bands using GL_TRIANGLE_STRIP/TRIANGLES.
26
+ """
27
+ def __init__(self, options: EngineOptions):
28
+ self.options = options
29
+ self.prog = 0
30
+ self.u_mvp = -1
31
+ self.u_color = -1
32
+ self.u_alpha = -1
33
+ self.u_offset = -1
34
+
35
+ def initialize(self) -> None:
36
+ self.prog = link_program(PATCH_VS, PATCH_FS)
37
+ self.u_mvp = glGetUniformLocation(self.prog, "u_mvp")
38
+ self.u_color = glGetUniformLocation(self.prog, "u_color")
39
+ self.u_alpha = glGetUniformLocation(self.prog, "u_alpha")
40
+ self.u_offset = glGetUniformLocation(self.prog, "u_layer_offset")
41
+
42
+ # Density accumulation (Triangles into R32F)
43
+ self.accum_prog = link_program(PATCH_VS, DENSITY_ACCUM_FS)
44
+ self.u_accum_mvp = glGetUniformLocation(self.accum_prog, "u_mvp")
45
+ self.u_accum_alpha = glGetUniformLocation(self.accum_prog, "u_alpha")
46
+ self.u_accum_weighted = glGetUniformLocation(self.accum_prog, "u_density_weighted")
47
+
48
+ def _create_buffers(self, layer: PatchLayer) -> GLPatchBuffers:
49
+ vao = glGenVertexArrays(1)
50
+ glBindVertexArray(vao)
51
+
52
+ vbo = glGenBuffers(1)
53
+ glBindBuffer(GL_ARRAY_BUFFER, vbo)
54
+ glEnableVertexAttribArray(0)
55
+ glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, C.c_void_p(0))
56
+
57
+ ebo = None
58
+ if layer.indices is not None:
59
+ ebo = glGenBuffers(1)
60
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo)
61
+
62
+ glBindVertexArray(0)
63
+ return GLPatchBuffers(vao, vbo, ebo)
64
+
65
+ def update_gpu_data(self, layer: PatchLayer, bufs: GLPatchBuffers) -> None:
66
+ if layer.vertices is None or len(layer.vertices) == 0: return
67
+
68
+ glBindBuffer(GL_ARRAY_BUFFER, bufs.vbo)
69
+ glBufferData(GL_ARRAY_BUFFER, layer.vertices.nbytes, layer.vertices, GL_STATIC_DRAW)
70
+
71
+ if layer.indices is not None and bufs.ebo is not None:
72
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bufs.ebo)
73
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, layer.indices.nbytes, layer.indices, GL_STATIC_DRAW)
74
+ bufs.count = len(layer.indices)
75
+ else:
76
+ bufs.count = len(layer.vertices)
77
+
78
+ layer.dirty.gpu_dirty = False
79
+
80
+ def draw(self, layer: PatchLayer, ctx: RenderContext) -> None:
81
+ if layer.vertices is None or len(layer.vertices) == 0: return
82
+
83
+ if not hasattr(layer, "_gl") or layer._gl is None:
84
+ layer._gl = self._create_buffers(layer)
85
+ layer.dirty.gpu_dirty = True
86
+
87
+ if layer.dirty.gpu_dirty:
88
+ self.update_gpu_data(layer, layer._gl)
89
+
90
+ glUseProgram(self.prog)
91
+ glUniformMatrix4fv(self.u_mvp, 1, GL_TRUE, ctx.mvp)
92
+
93
+ overrides = self.options.visual.overrides
94
+ alpha = ctx.global_alpha * layer.style.alpha * overrides.alpha_multiplier
95
+ glUniform1f(self.u_alpha, float(alpha))
96
+ glUniform2f(self.u_offset, *layer.translation)
97
+
98
+ # Draw face
99
+ if layer.style.face_color is not None:
100
+ glUniform4f(self.u_color, *layer.style.face_color)
101
+ glBindVertexArray(layer._gl.vao)
102
+
103
+ mode = GL_TRIANGLE_STRIP
104
+ if layer.mode == "triangles": mode = GL_TRIANGLES
105
+
106
+ if layer._gl.ebo is not None:
107
+ glDrawElements(mode, layer._gl.count, GL_UNSIGNED_INT, None)
108
+ else:
109
+ glDrawArrays(mode, 0, layer._gl.count)
110
+
111
+ glBindVertexArray(0)
112
+ glUseProgram(0)
113
+
114
+ def draw_density(self, layer: PatchLayer, ctx: RenderContext) -> None:
115
+ """Accumulate patch area into density heatmap."""
116
+ if layer.vertices is None or len(layer.vertices) == 0: return
117
+
118
+ # 1. Resource Management
119
+ if not hasattr(layer, "_gl") or layer._gl is None:
120
+ layer._gl = self._create_buffers(layer)
121
+ layer.dirty.gpu_dirty = True
122
+ if layer.dirty.gpu_dirty:
123
+ self.update_gpu_data(layer, layer._gl)
124
+
125
+ # 2. Setup Shaders
126
+ glUseProgram(self.accum_prog)
127
+ glUniformMatrix4fv(self.u_accum_mvp, 1, GL_TRUE, ctx.mvp)
128
+
129
+ overrides = self.options.visual.overrides
130
+ if self.options.density_weighted:
131
+ glUniform1i(self.u_accum_weighted, 1)
132
+ alpha = ctx.global_alpha * layer.style.alpha * overrides.alpha_multiplier
133
+ glUniform1f(self.u_accum_alpha, float(alpha))
134
+ else:
135
+ glUniform1i(self.u_accum_weighted, 0)
136
+ glUniform1f(self.u_accum_alpha, 1.0)
137
+
138
+ # 3. Draw call
139
+ glBindVertexArray(layer._gl.vao)
140
+ mode = GL_TRIANGLE_STRIP
141
+ if layer.mode == "triangles": mode = GL_TRIANGLES
142
+
143
+ if layer._gl.ebo is not None:
144
+ glDrawElements(mode, layer._gl.count, GL_UNSIGNED_INT, None)
145
+ else:
146
+ glDrawArrays(mode, 0, layer._gl.count)
147
+
148
+ glBindVertexArray(0)
149
+ glUseProgram(0)
@@ -0,0 +1,230 @@
1
+ from __future__ import annotations
2
+ import ctypes as C
3
+ import numpy as np
4
+ from OpenGL.GL import *
5
+ from typing import TYPE_CHECKING
6
+ from dataclasses import dataclass
7
+
8
+ from ..utils.gl_utils import link_program
9
+ from ..utils.shaders import (
10
+ WIDE_SEGMENT_INSTANCED_VS,
11
+ WIDE_SEGMENT_INSTANCED_FS,
12
+ WIDE_SEGMENT_DENSITY_FS,
13
+ )
14
+
15
+ if TYPE_CHECKING:
16
+ from ..core.layers import PolylineLayer
17
+ from ..core.context import RenderContext
18
+ from ..options import EngineOptions
19
+
20
+ @dataclass
21
+ class GLWideSegmentBuffers:
22
+ vao: int = 0
23
+ vbo_quad: int = 0
24
+ vbo_inst: int = 0
25
+ ebo: int = 0
26
+ instance_count: int = 0
27
+
28
+ class PolylineRenderer:
29
+ """
30
+ High-performance renderer for PolylineLayer using GPU Instancing.
31
+ Replaces the older CPU-side expansion logic.
32
+ """
33
+ def __init__(self, options: EngineOptions):
34
+ self.options = options
35
+
36
+ self.prog = 0
37
+ self.u_ndc_scale = -1
38
+ self.u_ndc_offset = -1
39
+ self.u_viewport = -1
40
+ self.u_width = -1
41
+ self.u_color = -1
42
+ self.u_alpha = -1
43
+ self.u_offset = -1
44
+ self.u_use_colormap = -1
45
+ self.u_scheme = -1
46
+ self.u_id_norm = -1
47
+
48
+ self.accum_prog = 0
49
+ self.u_acc_ndc_scale = -1
50
+ self.u_acc_ndc_offset = -1
51
+ self.u_acc_viewport = -1
52
+ self.u_acc_width = -1
53
+ self.u_acc_alpha = -1
54
+ self.u_acc_weighted = -1
55
+ self.u_acc_offset = -1
56
+
57
+ def initialize(self) -> None:
58
+ self.prog = link_program(WIDE_SEGMENT_INSTANCED_VS, WIDE_SEGMENT_INSTANCED_FS)
59
+ self.u_ndc_scale = glGetUniformLocation(self.prog, "u_ndc_scale")
60
+ self.u_ndc_offset = glGetUniformLocation(self.prog, "u_ndc_offset")
61
+ self.u_viewport = glGetUniformLocation(self.prog, "u_viewport_size")
62
+ self.u_width = glGetUniformLocation(self.prog, "u_width")
63
+ self.u_color = glGetUniformLocation(self.prog, "u_color")
64
+ self.u_alpha = glGetUniformLocation(self.prog, "u_alpha")
65
+ self.u_offset = glGetUniformLocation(self.prog, "u_layer_offset")
66
+ self.u_use_colormap = glGetUniformLocation(self.prog, "u_use_colormap")
67
+ self.u_scheme = glGetUniformLocation(self.prog, "u_scheme")
68
+ self.u_id_norm = glGetUniformLocation(self.prog, "u_id_norm")
69
+
70
+ self.accum_prog = link_program(WIDE_SEGMENT_INSTANCED_VS, WIDE_SEGMENT_DENSITY_FS)
71
+ self.u_acc_ndc_scale = glGetUniformLocation(self.accum_prog, "u_ndc_scale")
72
+ self.u_acc_ndc_offset = glGetUniformLocation(self.accum_prog, "u_ndc_offset")
73
+ self.u_acc_viewport = glGetUniformLocation(self.accum_prog, "u_viewport_size")
74
+ self.u_acc_width = glGetUniformLocation(self.accum_prog, "u_width")
75
+ self.u_acc_alpha = glGetUniformLocation(self.accum_prog, "u_alpha")
76
+ self.u_acc_weighted = glGetUniformLocation(self.accum_prog, "u_density_weighted")
77
+ self.u_acc_offset = glGetUniformLocation(self.accum_prog, "u_layer_offset")
78
+
79
+ def _create_buffers(self, layer: PolylineLayer) -> GLWideSegmentBuffers:
80
+ vao = glGenVertexArrays(1)
81
+ glBindVertexArray(vao)
82
+
83
+ # Static quad corners: (t, side)
84
+ # t in [0, 1] goes from start to end of segment
85
+ # side in [-0.5, 0.5] handles expansion around centerline
86
+ quad = np.array([
87
+ [0.0, -0.5],
88
+ [0.0, 0.5],
89
+ [1.0, -0.5],
90
+ [1.0, 0.5],
91
+ ], dtype=np.float32)
92
+
93
+ indices = np.array([0, 1, 2, 2, 1, 3], dtype=np.uint16)
94
+
95
+ vbo_quad = glGenBuffers(1)
96
+ glBindBuffer(GL_ARRAY_BUFFER, vbo_quad)
97
+ glBufferData(GL_ARRAY_BUFFER, quad.nbytes, quad, GL_STATIC_DRAW)
98
+ glEnableVertexAttribArray(0)
99
+ glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 8, C.c_void_p(0))
100
+
101
+ ebo = glGenBuffers(1)
102
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo)
103
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.nbytes, indices, GL_STATIC_DRAW)
104
+
105
+ # Per-instance segment buffer: [p0.x, p0.y, p1.x, p1.y]
106
+ vbo_inst = glGenBuffers(1)
107
+ glBindBuffer(GL_ARRAY_BUFFER, vbo_inst)
108
+ glBufferData(GL_ARRAY_BUFFER, 16, None, GL_STREAM_DRAW) # Initial tiny allocation
109
+
110
+ stride = 4 * 4
111
+ # i_p0 (location 1)
112
+ glEnableVertexAttribArray(1)
113
+ glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, stride, C.c_void_p(0))
114
+ glVertexAttribDivisor(1, 1)
115
+
116
+ # i_p1 (location 2)
117
+ glEnableVertexAttribArray(2)
118
+ glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, stride, C.c_void_p(8))
119
+ glVertexAttribDivisor(2, 1)
120
+
121
+ glBindVertexArray(0)
122
+ return GLWideSegmentBuffers(
123
+ vao=vao,
124
+ vbo_quad=vbo_quad,
125
+ vbo_inst=vbo_inst,
126
+ ebo=ebo,
127
+ instance_count=0,
128
+ )
129
+
130
+ def update_gpu_data(self, layer: PolylineLayer, bufs: GLWideSegmentBuffers) -> None:
131
+ if layer.pts is None or len(layer.pts) < 2:
132
+ bufs.instance_count = 0
133
+ layer.dirty.gpu_dirty = False
134
+ return
135
+
136
+ # Prepare segment data [p0x, p0y, p1x, p1y]
137
+ pts = np.ascontiguousarray(layer.pts, dtype=np.float32)
138
+ segs = np.empty((len(pts) - 1, 4), dtype=np.float32)
139
+ segs[:, 0:2] = pts[:-1]
140
+ segs[:, 2:4] = pts[1:]
141
+
142
+ glBindBuffer(GL_ARRAY_BUFFER, bufs.vbo_inst)
143
+
144
+ # Buffer Orphaning for performance
145
+ glBufferData(GL_ARRAY_BUFFER, segs.nbytes, None, GL_STREAM_DRAW)
146
+ glBufferSubData(GL_ARRAY_BUFFER, 0, segs.nbytes, segs)
147
+
148
+ bufs.instance_count = len(segs)
149
+ layer.dirty.gpu_dirty = False
150
+
151
+ def draw(self, layer: PolylineLayer, ctx: RenderContext, id_norm: float = 0.0) -> None:
152
+ if layer.pts is None or len(layer.pts) < 2:
153
+ return
154
+
155
+ if not hasattr(layer, "_gl") or layer._gl is None:
156
+ layer._gl = self._create_buffers(layer)
157
+ layer.dirty.gpu_dirty = True
158
+
159
+ if layer.dirty.gpu_dirty:
160
+ self.update_gpu_data(layer, layer._gl)
161
+
162
+ overrides = self.options.visual.overrides
163
+ width_px = max(1.0, layer.style.line_width * overrides.line_width_multiplier)
164
+ alpha = ctx.global_alpha * layer.style.alpha * overrides.alpha_multiplier
165
+ color = layer.style.color if layer.style.color is not None else (0.0, 0.0, 0.0, 1.0)
166
+
167
+ glUseProgram(self.prog)
168
+ glUniform2f(self.u_ndc_scale, *ctx.ndc_scale)
169
+ glUniform2f(self.u_ndc_offset, *ctx.ndc_offset)
170
+ glUniform2f(self.u_viewport, float(ctx.fb_width), float(ctx.fb_height))
171
+ glUniform1f(self.u_width, float(width_px))
172
+ glUniform4f(self.u_color, *color)
173
+ glUniform1f(self.u_alpha, float(alpha))
174
+ glUniform2f(self.u_offset, *layer.translation)
175
+
176
+ glUniform1i(self.u_use_colormap, 1 if self.options.line_colormap_enabled else 0)
177
+ glUniform1i(self.u_scheme, self.options.density_scheme_index)
178
+ glUniform1f(self.u_id_norm, float(id_norm))
179
+
180
+ glBindVertexArray(layer._gl.vao)
181
+ glDrawElementsInstanced(
182
+ GL_TRIANGLES,
183
+ 6,
184
+ GL_UNSIGNED_SHORT,
185
+ C.c_void_p(0),
186
+ layer._gl.instance_count
187
+ )
188
+ glBindVertexArray(0)
189
+ glUseProgram(0)
190
+
191
+ def draw_density(self, layer: PolylineLayer, ctx: RenderContext) -> None:
192
+ if layer.pts is None or len(layer.pts) < 2:
193
+ return
194
+
195
+ if not hasattr(layer, "_gl") or layer._gl is None:
196
+ layer._gl = self._create_buffers(layer)
197
+ layer.dirty.gpu_dirty = True
198
+
199
+ if layer.dirty.gpu_dirty:
200
+ self.update_gpu_data(layer, layer._gl)
201
+
202
+ overrides = self.options.visual.overrides
203
+ width_px = max(1.0, layer.style.line_width * overrides.line_width_multiplier)
204
+
205
+ if self.options.density_weighted:
206
+ alpha = ctx.global_alpha * layer.style.alpha * overrides.alpha_multiplier
207
+ weighted = 1
208
+ else:
209
+ alpha = 1.0
210
+ weighted = 0
211
+
212
+ glUseProgram(self.accum_prog)
213
+ glUniform2f(self.u_acc_ndc_scale, *ctx.ndc_scale)
214
+ glUniform2f(self.u_acc_ndc_offset, *ctx.ndc_offset)
215
+ glUniform2f(self.u_acc_viewport, float(ctx.fb_width), float(ctx.fb_height))
216
+ glUniform1f(self.u_acc_width, float(width_px))
217
+ glUniform1f(self.u_acc_alpha, float(alpha))
218
+ glUniform1i(self.u_acc_weighted, weighted)
219
+ glUniform2f(self.u_acc_offset, *layer.translation)
220
+
221
+ glBindVertexArray(layer._gl.vao)
222
+ glDrawElementsInstanced(
223
+ GL_TRIANGLES,
224
+ 6,
225
+ GL_UNSIGNED_SHORT,
226
+ C.c_void_p(0),
227
+ layer._gl.instance_count
228
+ )
229
+ glBindVertexArray(0)
230
+ glUseProgram(0)