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.
- glplot/__init__.py +6 -0
- glplot/backend.py +15 -0
- glplot/controllers.py +69 -0
- glplot/core/__init__.py +0 -0
- glplot/core/context.py +50 -0
- glplot/core/layers.py +163 -0
- glplot/core/legacy.py +98 -0
- glplot/engine.py +1270 -0
- glplot/managers/__init__.py +0 -0
- glplot/managers/axis.py +66 -0
- glplot/managers/effects.py +343 -0
- glplot/managers/hud.py +510 -0
- glplot/managers/hud_state.py +95 -0
- glplot/managers/picking.py +174 -0
- glplot/managers/renderer_manager.py +158 -0
- glplot/options.py +120 -0
- glplot/policy.py +108 -0
- glplot/pyplot.py +735 -0
- glplot/renderers/__init__.py +0 -0
- glplot/renderers/axis.py +126 -0
- glplot/renderers/base.py +40 -0
- glplot/renderers/density.py +120 -0
- glplot/renderers/exact.py +215 -0
- glplot/renderers/interaction.py +77 -0
- glplot/renderers/line_family.py +250 -0
- glplot/renderers/patch.py +149 -0
- glplot/renderers/polyline.py +230 -0
- glplot/renderers/scatter.py +185 -0
- glplot/renderers/text.py +72 -0
- glplot/scratch/__init__.py +0 -0
- glplot/utils/__init__.py +0 -0
- glplot/utils/export.py +112 -0
- glplot/utils/gl_utils.py +32 -0
- glplot/utils/mpl_bridge.py +60 -0
- glplot/utils/shaders.py +889 -0
- glplot-0.1.0.dist-info/METADATA +75 -0
- glplot-0.1.0.dist-info/RECORD +39 -0
- glplot-0.1.0.dist-info/WHEEL +4 -0
- glplot-0.1.0.dist-info/licenses/LICENSE +0 -0
|
@@ -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)
|