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
|
File without changes
|
glplot/renderers/axis.py
ADDED
|
@@ -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)
|
glplot/renderers/base.py
ADDED
|
@@ -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)
|