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.
File without changes
@@ -0,0 +1,126 @@
1
+ from __future__ import annotations
2
+ import numpy as np
3
+ from OpenGL.GL import *
4
+ from typing import TYPE_CHECKING, Optional
5
+
6
+ from ..utils.shaders import STRIP_VS, STRIP_FS
7
+ from ..utils.gl_utils import link_program
8
+
9
+ if TYPE_CHECKING:
10
+ from ..managers.axis import AxisManager
11
+ from ..core.context import RenderContext
12
+ from ..options import EngineOptions
13
+
14
+ class AxisRenderer:
15
+ """
16
+ Specialized renderer for the plot framework: grid, spines, and ticks.
17
+ """
18
+ def __init__(self, options: EngineOptions):
19
+ self.options = options
20
+ self.prog = 0
21
+ self.u_mvp = -1
22
+ self.u_color = -1
23
+ self.u_alpha = -1
24
+
25
+ # Temp buffer for line drawing
26
+ self.vbo = 0
27
+ self.vao = 0
28
+
29
+ def initialize(self) -> None:
30
+ self.prog = link_program(STRIP_VS, STRIP_FS)
31
+ self.u_mvp = glGetUniformLocation(self.prog, "u_mvp")
32
+ self.u_color = glGetUniformLocation(self.prog, "u_color")
33
+ self.u_alpha = glGetUniformLocation(self.prog, "u_alpha")
34
+
35
+ self.vao = glGenVertexArrays(1)
36
+ self.vbo = glGenBuffers(1)
37
+
38
+ glBindVertexArray(self.vao)
39
+ glBindBuffer(GL_ARRAY_BUFFER, self.vbo)
40
+ glEnableVertexAttribArray(0)
41
+ glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, None)
42
+ glBindVertexArray(0)
43
+
44
+ def draw(self, axis: AxisManager, ctx: RenderContext) -> None:
45
+ # Check overall visibility
46
+ if not any([self.options.axis_show_grid, self.options.axis_show_frame, self.options.axis_show_labels]):
47
+ return
48
+
49
+ glUseProgram(self.prog)
50
+ glUniformMatrix4fv(self.u_mvp, 1, GL_TRUE, ctx.mvp)
51
+
52
+ glBindVertexArray(self.vao)
53
+ glBindBuffer(GL_ARRAY_BUFFER, self.vbo)
54
+
55
+ win = ctx.window_world
56
+
57
+ # 1. Draw Grid
58
+ if self.options.axis_show_grid:
59
+ c = self.options.axis_grid_color
60
+ glUniform4f(self.u_color, c[0], c[1], c[2], self.options.axis_grid_alpha)
61
+ glUniform1f(self.u_alpha, 1.0)
62
+
63
+ grid_lines = []
64
+ # Vertical lines (X-ticks)
65
+ for x in axis.ticks_x.major:
66
+ grid_lines.extend([(x, win[2]), (x, win[3])])
67
+ # Horizontal lines (Y-ticks)
68
+ for y in axis.ticks_y.major:
69
+ grid_lines.extend([(win[0], y), (win[1], y)])
70
+
71
+ if grid_lines:
72
+ data = np.array(grid_lines, dtype=np.float32)
73
+ glBufferData(GL_ARRAY_BUFFER, data.nbytes, data, GL_STREAM_DRAW)
74
+ glDrawArrays(GL_LINES, 0, len(grid_lines))
75
+
76
+ # 2. Draw Spines (Frame)
77
+ if self.options.axis_show_frame:
78
+ glUniform4f(self.u_color, 0.2, 0.2, 0.2, 1.0) # Dark spines
79
+ frame = [
80
+ (win[0], win[2]), (win[1], win[2]),
81
+ (win[1], win[2]), (win[1], win[3]),
82
+ (win[1], win[3]), (win[0], win[3]),
83
+ (win[0], win[3]), (win[0], win[2])
84
+ ]
85
+ data_frame = np.array(frame, dtype=np.float32)
86
+ glBufferData(GL_ARRAY_BUFFER, data_frame.nbytes, data_frame, GL_STREAM_DRAW)
87
+ glDrawArrays(GL_LINES, 0, 8)
88
+
89
+ glBindVertexArray(0)
90
+ glUseProgram(0)
91
+
92
+ # 3. Draw Axis Labels (Scale)
93
+ if self.options.axis_show_labels:
94
+ self._draw_labels(axis, ctx)
95
+
96
+ def _draw_labels(self, axis: AxisManager, ctx: RenderContext) -> None:
97
+ """Draw numeric labels along the axes."""
98
+ try:
99
+ import imgui
100
+ except ImportError:
101
+ return
102
+
103
+ draw_list = imgui.get_background_draw_list()
104
+ color = imgui.get_color_u32_rgba(0.2, 0.2, 0.2, 1.0)
105
+ win = ctx.window_world
106
+
107
+ # Helper to project world to screen
108
+ def project(wx, wy):
109
+ pos_world = np.array([wx, wy, 0.0, 1.0], dtype=np.float32)
110
+ pos_ndc = ctx.mvp @ pos_world
111
+ if pos_ndc[3] != 0: pos_ndc /= pos_ndc[3]
112
+ screen_x = (pos_ndc[0] + 1.0) * 0.5 * ctx.width_px
113
+ screen_y = (1.0 - pos_ndc[1]) * 0.5 * ctx.height_px
114
+ return screen_x, screen_y
115
+
116
+ # X-Axis Labels (along bottom)
117
+ for val, label in zip(axis.ticks_x.major, axis.ticks_x.labels):
118
+ sx, sy = project(val, win[2])
119
+ # Offset labels slightly below the spine
120
+ draw_list.add_text(sx - 15, sy + 5, color, label)
121
+
122
+ # Y-Axis Labels (along left)
123
+ for val, label in zip(axis.ticks_y.major, axis.ticks_y.labels):
124
+ sx, sy = project(win[0], val)
125
+ # Offset labels slightly to the left of the spine
126
+ draw_list.add_text(sx - 45, sy - 7, color, label)
@@ -0,0 +1,40 @@
1
+ from __future__ import annotations
2
+ from dataclasses import dataclass
3
+ from typing import Tuple
4
+
5
+ @dataclass
6
+ class GLLineBuffers:
7
+ vao: int = 0
8
+ vbo_base: int = 0
9
+ vbo_ab: int = 0
10
+ vbo_col: int = 0
11
+ has_color: bool = False
12
+
13
+ def render(self, count: int) -> None:
14
+ from OpenGL.GL import glBindVertexArray, glDrawArraysInstanced, GL_LINES
15
+ if self.vao and count > 0:
16
+ glBindVertexArray(self.vao)
17
+ glDrawArraysInstanced(GL_LINES, 0, 2, count)
18
+ glBindVertexArray(0)
19
+
20
+ @dataclass
21
+ class GLScatterBuffers:
22
+ vao: int = 0
23
+ vbo_pts: int = 0
24
+ vbo_col: int = 0
25
+ count: int = 0
26
+ size: float = 5.0
27
+
28
+ @dataclass
29
+ class GLStripBuffers:
30
+ vao: int = 0
31
+ vbo_pts: int = 0
32
+ count: int = 0
33
+ color: Tuple[float, float, float, float] = (0, 0, 0, 1)
34
+
35
+ @dataclass
36
+ class GLOffscreenTarget:
37
+ fbo: int = 0
38
+ tex: int = 0
39
+ width: int = 0
40
+ height: int = 0
@@ -0,0 +1,120 @@
1
+ from __future__ import annotations
2
+ import numpy as np
3
+ from OpenGL.GL import *
4
+ from typing import Tuple, Optional, TYPE_CHECKING
5
+
6
+ from ..utils.shaders import INTERACTION_FULLSCREEN_VS, DENSITY_RESOLVE_FS
7
+ from ..utils.gl_utils import link_program
8
+ from .base import GLOffscreenTarget
9
+
10
+ if TYPE_CHECKING:
11
+ from ..options import EngineOptions
12
+ from ..core.context import RenderContext
13
+
14
+ class DensityRenderer:
15
+ """
16
+ Modular Density Manager for Phase 5.
17
+
18
+ Coordinates the accumulation of density data from multiple primitive
19
+ renderers into a shared R32F texture, then resolves it into a heatmap.
20
+ """
21
+
22
+ def __init__(self, plot: "GPULinePlot"):
23
+ self.plot = plot
24
+ self.options = plot.options
25
+
26
+ # Resolve pass
27
+ self.resolve_prog = 0
28
+ self.u_resolve_tex = -1
29
+ self.u_resolve_gain = -1
30
+ self.u_resolve_scheme = -1
31
+ self.resolve_vao = 0
32
+
33
+ self.accum_target = GLOffscreenTarget()
34
+ self._clear_zero = np.array([0.0], dtype=np.float32)
35
+
36
+ def initialize(self, fb_width: int, fb_height: int) -> None:
37
+ """Initialize shaders and framebuffer targets."""
38
+ self.resolve_prog = link_program(INTERACTION_FULLSCREEN_VS, DENSITY_RESOLVE_FS)
39
+ self.u_resolve_tex = glGetUniformLocation(self.resolve_prog, "u_tex")
40
+ self.u_resolve_gain = glGetUniformLocation(self.resolve_prog, "u_gain")
41
+ self.u_resolve_log_scale = glGetUniformLocation(self.resolve_prog, "u_log_scale")
42
+ self.u_resolve_scheme = glGetUniformLocation(self.resolve_prog, "u_scheme")
43
+ self.resolve_vao = glGenVertexArrays(1)
44
+
45
+ self.rebuild_target(fb_width, fb_height)
46
+
47
+ def rebuild_target(self, fb_width: int, fb_height: int) -> None:
48
+ """Create/Resize the R32F accumulation texture."""
49
+ if self.accum_target.fbo:
50
+ glDeleteFramebuffers(1, [self.accum_target.fbo])
51
+ glDeleteTextures(1, [self.accum_target.tex])
52
+
53
+ scale = max(0.05, float(self.options.density_resolution_scale))
54
+ w = max(1, int(round(fb_width * scale)))
55
+ h = max(1, int(round(fb_height * scale)))
56
+
57
+ tex = glGenTextures(1)
58
+ glBindTexture(GL_TEXTURE_2D, tex)
59
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_R32F, w, h, 0, GL_RED, GL_FLOAT, None)
60
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
61
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
62
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
63
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
64
+
65
+ fbo = glGenFramebuffers(1)
66
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo)
67
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0)
68
+
69
+ status = glCheckFramebufferStatus(GL_FRAMEBUFFER)
70
+ glBindFramebuffer(GL_FRAMEBUFFER, 0)
71
+ if status != GL_FRAMEBUFFER_COMPLETE:
72
+ raise RuntimeError("Density accumulation framebuffer is incomplete")
73
+
74
+ self.accum_target = GLOffscreenTarget(fbo=fbo, tex=tex, width=w, height=h)
75
+
76
+ def begin_accum(self) -> None:
77
+ """Prepare the accumulation target for a new frame."""
78
+ glBindFramebuffer(GL_FRAMEBUFFER, self.accum_target.fbo)
79
+ glViewport(0, 0, self.accum_target.width, self.accum_target.height)
80
+ glClearBufferfv(GL_COLOR, 0, self._clear_zero)
81
+
82
+ # DENSITY ALWAYS NEEDS ADDITIVE BLENDING for accumulation
83
+ glEnable(GL_BLEND)
84
+ glBlendFunc(GL_ONE, GL_ONE)
85
+
86
+ # Handle clipping state if enabled globally
87
+ if self.options.enable_clipping_optimization:
88
+ glEnable(GL_CLIP_DISTANCE0)
89
+ glEnable(GL_CLIP_DISTANCE1)
90
+ glEnable(GL_CLIP_DISTANCE2)
91
+ glEnable(GL_CLIP_DISTANCE3)
92
+ else:
93
+ glDisable(GL_CLIP_DISTANCE0)
94
+ glDisable(GL_CLIP_DISTANCE1)
95
+ glDisable(GL_CLIP_DISTANCE2)
96
+ glDisable(GL_CLIP_DISTANCE3)
97
+
98
+ def resolve(self, target_fbo: int = 0, target_size: Optional[Tuple[int, int]] = None) -> None:
99
+ """Resolve the accumulated density into a color heatmap in the target FBO."""
100
+ glBindFramebuffer(GL_FRAMEBUFFER, target_fbo)
101
+
102
+ # Robust Viewport Management (Fix for 1/4 size rendering on HighDPI)
103
+ if target_size is not None:
104
+ glViewport(0, 0, int(target_size[0]), int(target_size[1]))
105
+ else:
106
+ glViewport(0, 0, self.plot.fb_width, self.plot.fb_height)
107
+
108
+ glDisable(GL_BLEND)
109
+ glUseProgram(self.resolve_prog)
110
+ glActiveTexture(GL_TEXTURE0)
111
+ glBindTexture(GL_TEXTURE_2D, self.accum_target.tex)
112
+ glUniform1i(self.u_resolve_tex, 0)
113
+ glUniform1f(self.u_resolve_gain, float(self.options.density_gain))
114
+ glUniform1f(self.u_resolve_log_scale, float(self.options.density_log_scale))
115
+ glUniform1i(self.u_resolve_scheme, self.options.density_scheme_index)
116
+
117
+ glBindVertexArray(self.resolve_vao)
118
+ glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)
119
+ glBindVertexArray(0)
120
+ glUseProgram(0)
@@ -0,0 +1,215 @@
1
+ from __future__ import annotations
2
+ import ctypes as C
3
+ import numpy as np
4
+ from OpenGL.GL import *
5
+ from typing import Tuple, TYPE_CHECKING
6
+ from ..utils.shaders import EXACT_LINES_VS, EXACT_LINES_FS, SCATTER_VS, SCATTER_FS, STRIP_VS, STRIP_FS
7
+ from ..utils.gl_utils import link_program
8
+ from ..utils.gl_utils import link_program
9
+ from .base import GLLineBuffers, GLScatterBuffers, GLStripBuffers
10
+
11
+ if TYPE_CHECKING:
12
+ from ..options import EngineOptions
13
+ from ..core.legacy import SceneData, LineDataset, ScatterDataset, StripDataset
14
+
15
+ class ExactLineRenderer:
16
+ def __init__(self, options: EngineOptions):
17
+ self.options = options
18
+ self.prog = 0
19
+ self.u_mvp = -1
20
+ self.u_xrange = -1
21
+ self.u_window = -1
22
+ self.u_use_color = -1
23
+ self.u_alpha = -1
24
+ self.u_enable_sub = -1
25
+ self.u_keep_prob = -1
26
+ self.buffers = GLLineBuffers()
27
+
28
+ self.scatter_prog = 0
29
+ self.u_scat_mvp = -1
30
+ self.u_scat_size = -1
31
+ self.u_scat_alpha = -1
32
+
33
+ self.strip_prog = 0
34
+ self.u_strip_mvp = -1
35
+ self.u_strip_color = -1
36
+ self.u_strip_alpha = -1
37
+
38
+ self.use_fp16_ab = True
39
+ self.use_packed_color = True
40
+
41
+ def initialize(self) -> None:
42
+ self.prog = link_program(EXACT_LINES_VS, EXACT_LINES_FS)
43
+ self.u_mvp = glGetUniformLocation(self.prog, "u_mvp")
44
+ self.u_xrange = glGetUniformLocation(self.prog, "u_xrange")
45
+ self.u_window = glGetUniformLocation(self.prog, "u_window")
46
+ self.u_use_color = glGetUniformLocation(self.prog, "u_use_color")
47
+ self.u_alpha = glGetUniformLocation(self.prog, "u_alpha")
48
+ self.u_enable_sub = glGetUniformLocation(self.prog, "u_enable_subsample")
49
+ self.u_keep_prob = glGetUniformLocation(self.prog, "u_keep_prob")
50
+ self.u_total_count = glGetUniformLocation(self.prog, "u_total_count")
51
+ self.u_use_colormap = glGetUniformLocation(self.prog, "u_use_colormap")
52
+ self.u_scheme = glGetUniformLocation(self.prog, "u_scheme")
53
+
54
+ self.scatter_prog = link_program(SCATTER_VS, SCATTER_FS)
55
+ self.u_scat_mvp = glGetUniformLocation(self.scatter_prog, "u_mvp")
56
+ self.u_scat_size = glGetUniformLocation(self.scatter_prog, "u_size")
57
+ self.u_scat_alpha = glGetUniformLocation(self.scatter_prog, "u_alpha")
58
+
59
+ self.strip_prog = link_program(STRIP_VS, STRIP_FS)
60
+ self.u_strip_mvp = glGetUniformLocation(self.strip_prog, "u_mvp")
61
+ self.u_strip_color = glGetUniformLocation(self.strip_prog, "u_color")
62
+ self.u_strip_alpha = glGetUniformLocation(self.strip_prog, "u_alpha")
63
+
64
+ self.buffers.vao = glGenVertexArrays(1)
65
+ glBindVertexArray(self.buffers.vao)
66
+
67
+ self.buffers.vbo_base = glGenBuffers(1)
68
+ glBindBuffer(GL_ARRAY_BUFFER, self.buffers.vbo_base)
69
+ t = np.array([0.0, 1.0], dtype=np.float32)
70
+ glBufferData(GL_ARRAY_BUFFER, t.nbytes, t, GL_STATIC_DRAW)
71
+ glEnableVertexAttribArray(0)
72
+ glVertexAttribPointer(0, 1, GL_FLOAT, GL_FALSE, 0, C.c_void_p(0))
73
+
74
+ self.buffers.vbo_ab = glGenBuffers(1)
75
+ glBindBuffer(GL_ARRAY_BUFFER, self.buffers.vbo_ab)
76
+ glBufferData(GL_ARRAY_BUFFER, 16, None, GL_STATIC_DRAW)
77
+ glEnableVertexAttribArray(1)
78
+ if self.use_fp16_ab:
79
+ glVertexAttribPointer(1, 2, GL_HALF_FLOAT, GL_FALSE, 0, C.c_void_p(0))
80
+ else:
81
+ glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, C.c_void_p(0))
82
+ glVertexAttribDivisor(1, 1)
83
+
84
+ self.buffers.vbo_col = glGenBuffers(1)
85
+ glBindBuffer(GL_ARRAY_BUFFER, self.buffers.vbo_col)
86
+ glBufferData(GL_ARRAY_BUFFER, 16, None, GL_STATIC_DRAW)
87
+ glEnableVertexAttribArray(2)
88
+ if self.use_packed_color:
89
+ glVertexAttribPointer(2, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, C.c_void_p(0))
90
+ else:
91
+ glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, 0, C.c_void_p(0))
92
+ glVertexAttribDivisor(2, 1)
93
+
94
+ glBindVertexArray(0)
95
+
96
+ def upload(self, dataset: LineDataset) -> None:
97
+ if dataset.ab is None:
98
+ return
99
+ dataset.validate()
100
+ glBindVertexArray(self.buffers.vao)
101
+
102
+ ab_u = dataset.ab.astype(np.float16) if self.use_fp16_ab else dataset.ab
103
+ glBindBuffer(GL_ARRAY_BUFFER, self.buffers.vbo_ab)
104
+ glBufferData(GL_ARRAY_BUFFER, ab_u.nbytes, ab_u, GL_STATIC_DRAW)
105
+
106
+ self.buffers.has_color = dataset.colors is not None
107
+ if self.buffers.has_color:
108
+ if self.use_packed_color:
109
+ cols_u8 = np.clip(dataset.colors * 255.0, 0, 255).astype(np.uint8, copy=False)
110
+ glBindBuffer(GL_ARRAY_BUFFER, self.buffers.vbo_col)
111
+ glBufferData(GL_ARRAY_BUFFER, cols_u8.nbytes, cols_u8, GL_STATIC_DRAW)
112
+ else:
113
+ glBindBuffer(GL_ARRAY_BUFFER, self.buffers.vbo_col)
114
+ glBufferData(GL_ARRAY_BUFFER, dataset.colors.nbytes, dataset.colors, GL_STATIC_DRAW)
115
+
116
+ glBindVertexArray(0)
117
+
118
+ def draw(
119
+ self,
120
+ scene: SceneData,
121
+ mvp: np.ndarray,
122
+ window: Tuple[float, float, float, float],
123
+ global_alpha: float,
124
+ lod_keep_prob: float,
125
+ clipping_enabled: bool,
126
+ ) -> None:
127
+ # 1. Draw main lines
128
+ dataset = scene.lines
129
+ if dataset.ab is not None and dataset.count > 0:
130
+ l, r, b, t = window
131
+ glUseProgram(self.prog)
132
+ glUniformMatrix4fv(self.u_mvp, 1, GL_TRUE, mvp)
133
+ glUniform2f(self.u_xrange, float(dataset.x_range[0]), float(dataset.x_range[1]))
134
+ glUniform4f(self.u_window, l, r, b, t)
135
+ glUniform1i(self.u_use_color, 1 if self.buffers.has_color else 0)
136
+ glUniform1f(self.u_alpha, float(global_alpha))
137
+ glUniform1i(self.u_enable_sub, 1 if lod_keep_prob < 1.0 else 0)
138
+ glUniform1f(self.u_keep_prob, float(lod_keep_prob))
139
+ glUniform1i(self.u_total_count, dataset.count)
140
+ glUniform1i(self.u_use_colormap, 1 if self.options.line_colormap_enabled else 0)
141
+ glUniform1i(self.u_scheme, self.options.density_scheme_index)
142
+
143
+ if clipping_enabled:
144
+ glEnable(GL_CLIP_DISTANCE0)
145
+ glEnable(GL_CLIP_DISTANCE1)
146
+ glEnable(GL_CLIP_DISTANCE2)
147
+ glEnable(GL_CLIP_DISTANCE3)
148
+ else:
149
+ glDisable(GL_CLIP_DISTANCE0)
150
+ glDisable(GL_CLIP_DISTANCE1)
151
+ glDisable(GL_CLIP_DISTANCE2)
152
+ glDisable(GL_CLIP_DISTANCE3)
153
+
154
+ glBindVertexArray(self.buffers.vao)
155
+ glDrawArraysInstanced(GL_LINES, 0, 2, dataset.count)
156
+ glBindVertexArray(0)
157
+ glUseProgram(0)
158
+
159
+ # 2. Draw scatters
160
+ if scene.scatters:
161
+ glUseProgram(self.scatter_prog)
162
+ glEnable(GL_PROGRAM_POINT_SIZE)
163
+ glUniformMatrix4fv(self.u_scat_mvp, 1, GL_TRUE, mvp)
164
+ glUniform1f(self.u_scat_alpha, float(global_alpha))
165
+ for scat in scene.scatters:
166
+ if not hasattr(scat, '_gl'):
167
+ scat._gl = self._create_scatter_buffers(scat)
168
+ glUniform1f(self.u_scat_size, float(scat.size))
169
+ glBindVertexArray(scat._gl.vao)
170
+ glDrawArrays(GL_POINTS, 0, scat._gl.count)
171
+ glBindVertexArray(0)
172
+ glUseProgram(0)
173
+
174
+ # 3. Draw line strips
175
+ if scene.strips:
176
+ glUseProgram(self.strip_prog)
177
+ glUniformMatrix4fv(self.u_strip_mvp, 1, GL_TRUE, mvp)
178
+ glUniform1f(self.u_strip_alpha, float(global_alpha))
179
+ for strip in scene.strips:
180
+ if not hasattr(strip, '_gl'):
181
+ strip._gl = self._create_strip_buffers(strip)
182
+ glUniform4f(self.u_strip_color, *strip.color)
183
+ glBindVertexArray(strip._gl.vao)
184
+ glDrawArrays(GL_LINE_STRIP, 0, strip._gl.count)
185
+ glBindVertexArray(0)
186
+ glUseProgram(0)
187
+
188
+ def _create_scatter_buffers(self, scat: ScatterDataset) -> GLScatterBuffers:
189
+ vao = glGenVertexArrays(1)
190
+ glBindVertexArray(vao)
191
+ vbo_pts = glGenBuffers(1)
192
+ glBindBuffer(GL_ARRAY_BUFFER, vbo_pts)
193
+ glBufferData(GL_ARRAY_BUFFER, scat.pts.nbytes, scat.pts, GL_STATIC_DRAW)
194
+ glEnableVertexAttribArray(0)
195
+ glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, C.c_void_p(0))
196
+
197
+ vbo_col = glGenBuffers(1)
198
+ glBindBuffer(GL_ARRAY_BUFFER, vbo_col)
199
+ glBufferData(GL_ARRAY_BUFFER, scat.colors.nbytes, scat.colors, GL_STATIC_DRAW)
200
+ glEnableVertexAttribArray(1)
201
+ glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, C.c_void_p(0))
202
+
203
+ glBindVertexArray(0)
204
+ return GLScatterBuffers(vao=vao, vbo_pts=vbo_pts, vbo_col=vbo_col, count=len(scat.pts), size=scat.size)
205
+
206
+ def _create_strip_buffers(self, strip: StripDataset) -> GLStripBuffers:
207
+ vao = glGenVertexArrays(1)
208
+ glBindVertexArray(vao)
209
+ vbo_pts = glGenBuffers(1)
210
+ glBindBuffer(GL_ARRAY_BUFFER, vbo_pts)
211
+ glBufferData(GL_ARRAY_BUFFER, strip.pts.nbytes, strip.pts, GL_STATIC_DRAW)
212
+ glEnableVertexAttribArray(0)
213
+ glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, C.c_void_p(0))
214
+ glBindVertexArray(0)
215
+ return GLStripBuffers(vao=vao, vbo_pts=vbo_pts, count=len(strip.pts), color=strip.color)
@@ -0,0 +1,77 @@
1
+ from __future__ import annotations
2
+ from OpenGL.GL import *
3
+ from typing import Tuple, TYPE_CHECKING
4
+ from ..utils.shaders import INTERACTION_FULLSCREEN_VS, CACHE_IMPOSTOR_FS
5
+ from ..utils.gl_utils import link_program
6
+ from .base import GLOffscreenTarget
7
+
8
+ if TYPE_CHECKING:
9
+ from ..options import EngineOptions
10
+ from ..engine import GPULinePlot
11
+
12
+ class InteractionRenderer:
13
+ """
14
+ Interaction rendering should never compromise frame pacing.
15
+
16
+ This renderer supports two lightweight interaction paths:
17
+ 1. cached impostor reprojection
18
+ 2. density/aggregate interaction image (placeholder for expansion)
19
+ """
20
+
21
+ def __init__(self, plot: "GPULinePlot"):
22
+ self.plot = plot
23
+ self.options = plot.options
24
+ self.cache_prog = 0
25
+ self.u_cache_tex = -1
26
+ self.u_cache_window = -1
27
+ self.u_cur_window = -1
28
+ self.cache_vao = 0
29
+ self.cache_target = GLOffscreenTarget()
30
+
31
+ def initialize(self, fb_width: int, fb_height: int) -> None:
32
+ self.cache_prog = link_program(INTERACTION_FULLSCREEN_VS, CACHE_IMPOSTOR_FS)
33
+ self.u_cache_tex = glGetUniformLocation(self.cache_prog, "u_tex")
34
+ self.u_cache_window = glGetUniformLocation(self.cache_prog, "u_cache_window")
35
+ self.u_cur_window = glGetUniformLocation(self.cache_prog, "u_cur_window")
36
+ self.cache_vao = glGenVertexArrays(1)
37
+ self.rebuild_cache_target(fb_width, fb_height)
38
+
39
+ def rebuild_cache_target(self, fb_width: int, fb_height: int) -> None:
40
+ if self.cache_target.fbo:
41
+ glDeleteFramebuffers(1, [self.cache_target.fbo])
42
+ glDeleteTextures(1, [self.cache_target.tex])
43
+
44
+ tex = glGenTextures(1)
45
+ glBindTexture(GL_TEXTURE_2D, tex)
46
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, fb_width, fb_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, None)
47
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
48
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
49
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
50
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
51
+
52
+ fbo = glGenFramebuffers(1)
53
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo)
54
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0)
55
+ glBindFramebuffer(GL_FRAMEBUFFER, 0)
56
+
57
+ self.cache_target = GLOffscreenTarget(fbo=fbo, tex=tex, width=fb_width, height=fb_height)
58
+
59
+ def draw_cached_impostor(
60
+ self,
61
+ capture_window: Tuple[float, float, float, float],
62
+ current_window: Tuple[float, float, float, float],
63
+ ) -> None:
64
+ glBindFramebuffer(GL_FRAMEBUFFER, 0)
65
+ glViewport(0, 0, self.plot.fb_width, self.plot.fb_height)
66
+ glEnable(GL_BLEND)
67
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
68
+ glUseProgram(self.cache_prog)
69
+ glActiveTexture(GL_TEXTURE0)
70
+ glBindTexture(GL_TEXTURE_2D, self.cache_target.tex)
71
+ glUniform1i(self.u_cache_tex, 0)
72
+ glUniform4f(self.u_cache_window, *capture_window)
73
+ glUniform4f(self.u_cur_window, *current_window)
74
+ glBindVertexArray(self.cache_vao)
75
+ glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)
76
+ glBindVertexArray(0)
77
+ glUseProgram(0)