basilisk-engine 0.1.26__py3-none-any.whl → 0.1.28__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.

Potentially problematic release.


This version of basilisk-engine might be problematic. Click here for more details.

Files changed (93) hide show
  1. basilisk/__init__.py +15 -15
  2. basilisk/audio/sound.py +27 -27
  3. basilisk/bsk_assets/cube.obj +48 -48
  4. basilisk/collisions/broad/broad_aabb.py +102 -102
  5. basilisk/collisions/broad/broad_bvh.py +137 -137
  6. basilisk/collisions/collider.py +95 -95
  7. basilisk/collisions/collider_handler.py +224 -224
  8. basilisk/collisions/narrow/contact_manifold.py +95 -95
  9. basilisk/collisions/narrow/dataclasses.py +34 -34
  10. basilisk/collisions/narrow/deprecated.py +46 -46
  11. basilisk/collisions/narrow/epa.py +91 -91
  12. basilisk/collisions/narrow/gjk.py +66 -66
  13. basilisk/collisions/narrow/graham_scan.py +24 -24
  14. basilisk/collisions/narrow/helper.py +29 -29
  15. basilisk/collisions/narrow/line_intersections.py +106 -106
  16. basilisk/collisions/narrow/sutherland_hodgman.py +75 -75
  17. basilisk/config.py +3 -3
  18. basilisk/draw/draw.py +100 -100
  19. basilisk/draw/draw_handler.py +175 -175
  20. basilisk/draw/font_renderer.py +28 -28
  21. basilisk/engine.py +169 -169
  22. basilisk/generic/abstract_bvh.py +15 -15
  23. basilisk/generic/abstract_custom.py +133 -133
  24. basilisk/generic/collisions.py +70 -72
  25. basilisk/generic/input_validation.py +66 -66
  26. basilisk/generic/math.py +6 -6
  27. basilisk/generic/matrices.py +35 -35
  28. basilisk/generic/meshes.py +72 -72
  29. basilisk/generic/quat.py +142 -142
  30. basilisk/generic/quat_methods.py +7 -7
  31. basilisk/generic/raycast_result.py +26 -26
  32. basilisk/generic/vec3.py +143 -143
  33. basilisk/input/__init__.py +0 -0
  34. basilisk/input/mouse.py +62 -0
  35. basilisk/input/path.py +14 -0
  36. basilisk/input_output/IO_handler.py +91 -91
  37. basilisk/input_output/clock.py +49 -49
  38. basilisk/input_output/keys.py +43 -43
  39. basilisk/input_output/mouse.py +89 -89
  40. basilisk/input_output/path.py +14 -14
  41. basilisk/mesh/cube.py +33 -33
  42. basilisk/mesh/mesh.py +233 -233
  43. basilisk/mesh/mesh_from_data.py +150 -150
  44. basilisk/mesh/model.py +271 -271
  45. basilisk/mesh/narrow_aabb.py +89 -89
  46. basilisk/mesh/narrow_bvh.py +91 -91
  47. basilisk/mesh/narrow_primative.py +23 -23
  48. basilisk/nodes/helper.py +28 -28
  49. basilisk/nodes/node.py +689 -689
  50. basilisk/nodes/node_handler.py +97 -97
  51. basilisk/particles/particle_handler.py +64 -64
  52. basilisk/particles/particle_renderer.py +92 -92
  53. basilisk/physics/impulse.py +112 -112
  54. basilisk/physics/physics_body.py +43 -43
  55. basilisk/physics/physics_engine.py +35 -35
  56. basilisk/render/batch.py +103 -103
  57. basilisk/render/camera.py +260 -260
  58. basilisk/render/chunk.py +106 -106
  59. basilisk/render/chunk_handler.py +165 -165
  60. basilisk/render/frame.py +95 -95
  61. basilisk/render/framebuffer.py +163 -289
  62. basilisk/render/image.py +87 -87
  63. basilisk/render/image_handler.py +120 -120
  64. basilisk/render/light.py +96 -96
  65. basilisk/render/light_handler.py +58 -58
  66. basilisk/render/material.py +219 -219
  67. basilisk/render/material_handler.py +131 -131
  68. basilisk/render/post_process.py +139 -139
  69. basilisk/render/shader.py +118 -118
  70. basilisk/render/shader_handler.py +83 -83
  71. basilisk/render/sky.py +120 -120
  72. basilisk/scene.py +279 -279
  73. basilisk/shaders/batch.frag +276 -276
  74. basilisk/shaders/batch.vert +115 -115
  75. basilisk/shaders/crt.frag +31 -31
  76. basilisk/shaders/draw.frag +22 -22
  77. basilisk/shaders/draw.vert +25 -25
  78. basilisk/shaders/filter.frag +22 -22
  79. basilisk/shaders/frame.frag +12 -12
  80. basilisk/shaders/frame.vert +13 -13
  81. basilisk/shaders/geometry.frag +8 -8
  82. basilisk/shaders/geometry.vert +41 -41
  83. basilisk/shaders/normal.frag +59 -59
  84. basilisk/shaders/normal.vert +96 -96
  85. basilisk/shaders/particle.frag +71 -71
  86. basilisk/shaders/particle.vert +84 -84
  87. basilisk/shaders/sky.frag +9 -9
  88. basilisk/shaders/sky.vert +13 -13
  89. {basilisk_engine-0.1.26.dist-info → basilisk_engine-0.1.28.dist-info}/METADATA +45 -38
  90. basilisk_engine-0.1.28.dist-info/RECORD +109 -0
  91. {basilisk_engine-0.1.26.dist-info → basilisk_engine-0.1.28.dist-info}/WHEEL +1 -1
  92. basilisk_engine-0.1.26.dist-info/RECORD +0 -106
  93. {basilisk_engine-0.1.26.dist-info → basilisk_engine-0.1.28.dist-info}/top_level.txt +0 -0
basilisk/render/frame.py CHANGED
@@ -1,96 +1,96 @@
1
- import numpy as np
2
- import moderngl as mgl
3
- from .shader import Shader
4
- from .framebuffer import Framebuffer
5
-
6
- from .post_process import PostProcess
7
-
8
- class Frame:
9
- shader: Shader=None
10
- vbo: mgl.Buffer=None
11
- vao: mgl.VertexArray=None
12
- framebuffer: mgl.Framebuffer=None
13
-
14
- def __init__(self, engine, scale: float=1.0, linear_filter: bool=False) -> None:
15
- """
16
- Basilisk render destination.
17
- Can be used to render to the screen or for headless rendering
18
- """
19
-
20
- self.engine = engine
21
- self.ctx = engine.ctx
22
-
23
- # Load framebuffer
24
- self.framebuffer = Framebuffer(self.engine, scale=scale, linear_filter=linear_filter)
25
- self.ping_pong_buffer = Framebuffer(self.engine, scale=scale, linear_filter=linear_filter)
26
-
27
- # Load Shaders
28
- self.shader = Shader(self.engine, self.engine.root + '/shaders/frame.vert', self.engine.root + '/shaders/frame.frag')
29
- self.engine.shader_handler.add(self.shader)
30
-
31
- # Load VAO
32
- self.vbo = self.ctx.buffer(np.array([[-1, -1, 0, 0, 0], [1, -1, 0, 1, 0], [1, 1, 0, 1, 1], [-1, 1, 0, 0, 1], [-1, -1, 0, 0, 0], [1, 1, 0, 1, 1]], dtype='f4'))
33
- self.vao = self.ctx.vertex_array(self.shader.program, [(self.vbo, '3f 2f', 'in_position', 'in_uv')], skip_errors=True)
34
-
35
- # TEMP TESTING
36
- self.post_processes = []
37
-
38
-
39
- def render(self) -> None:
40
- """
41
- Renders the current frame to the screen
42
- """
43
-
44
- for process in self.post_processes:
45
- self.ping_pong_buffer = process.apply(self.framebuffer, self.ping_pong_buffer)
46
-
47
- temp = self.framebuffer
48
- self.framebuffer = self.ping_pong_buffer
49
- self.ping_pong_buffer = temp
50
-
51
- self.ctx.screen.use()
52
- self.shader.program['screenTexture'] = 0
53
- self.framebuffer.texture.use(location=0)
54
- self.vao.render()
55
-
56
-
57
- def use(self) -> None:
58
- """
59
- Uses the frame as a render target
60
- """
61
-
62
- self.framebuffer.use()
63
-
64
- def add_post_process(self, post_process: PostProcess) -> PostProcess:
65
- """
66
- Add a post process to the frames post process stack
67
- """
68
-
69
- self.post_processes.append(post_process)
70
- return post_process
71
-
72
- def save(self, destination: str=None) -> None:
73
- """
74
- Saves the frame as an image to the given file destination
75
- """
76
-
77
- self.framebuffer.save(destination)
78
-
79
- def clear(self):
80
- self.framebuffer.clear()
81
-
82
- def resize(self, size: tuple[int]=None) -> None:
83
- """
84
- Resize the frame to the given size. None for window size
85
- """
86
-
87
- self.framebuffer.resize()
88
- self.ping_pong_buffer.resize()
89
-
90
- def __del__(self) -> None:
91
- """
92
- Releases memory used by the frame
93
- """
94
-
95
- if self.vbo: self.vbo.release()
1
+ import numpy as np
2
+ import moderngl as mgl
3
+ from .shader import Shader
4
+ from .framebuffer import Framebuffer
5
+
6
+ from .post_process import PostProcess
7
+
8
+ class Frame:
9
+ shader: Shader=None
10
+ vbo: mgl.Buffer=None
11
+ vao: mgl.VertexArray=None
12
+ framebuffer: mgl.Framebuffer=None
13
+
14
+ def __init__(self, engine, scale: float=1.0, linear_filter: bool=False) -> None:
15
+ """
16
+ Basilisk render destination.
17
+ Can be used to render to the screen or for headless rendering
18
+ """
19
+
20
+ self.engine = engine
21
+ self.ctx = engine.ctx
22
+
23
+ # Load framebuffer
24
+ self.framebuffer = Framebuffer(self.engine, scale=scale, linear_filter=linear_filter)
25
+ self.ping_pong_buffer = Framebuffer(self.engine, scale=scale, linear_filter=linear_filter)
26
+
27
+ # Load Shaders
28
+ self.shader = Shader(self.engine, self.engine.root + '/shaders/frame.vert', self.engine.root + '/shaders/frame.frag')
29
+ self.engine.shader_handler.add(self.shader)
30
+
31
+ # Load VAO
32
+ self.vbo = self.ctx.buffer(np.array([[-1, -1, 0, 0, 0], [1, -1, 0, 1, 0], [1, 1, 0, 1, 1], [-1, 1, 0, 0, 1], [-1, -1, 0, 0, 0], [1, 1, 0, 1, 1]], dtype='f4'))
33
+ self.vao = self.ctx.vertex_array(self.shader.program, [(self.vbo, '3f 2f', 'in_position', 'in_uv')], skip_errors=True)
34
+
35
+ # TEMP TESTING
36
+ self.post_processes = []
37
+
38
+
39
+ def render(self) -> None:
40
+ """
41
+ Renders the current frame to the screen
42
+ """
43
+
44
+ for process in self.post_processes:
45
+ self.ping_pong_buffer = process.apply(self.framebuffer, self.ping_pong_buffer)
46
+
47
+ temp = self.framebuffer
48
+ self.framebuffer = self.ping_pong_buffer
49
+ self.ping_pong_buffer = temp
50
+
51
+ self.ctx.screen.use()
52
+ self.shader.program['screenTexture'] = 0
53
+ self.framebuffer.texture.use(location=0)
54
+ self.vao.render()
55
+
56
+
57
+ def use(self) -> None:
58
+ """
59
+ Uses the frame as a render target
60
+ """
61
+
62
+ self.framebuffer.use()
63
+
64
+ def add_post_process(self, post_process: PostProcess) -> PostProcess:
65
+ """
66
+ Add a post process to the frames post process stack
67
+ """
68
+
69
+ self.post_processes.append(post_process)
70
+ return post_process
71
+
72
+ def save(self, destination: str=None) -> None:
73
+ """
74
+ Saves the frame as an image to the given file destination
75
+ """
76
+
77
+ self.framebuffer.save(destination)
78
+
79
+ def clear(self):
80
+ self.framebuffer.clear()
81
+
82
+ def resize(self, size: tuple[int]=None) -> None:
83
+ """
84
+ Resize the frame to the given size. None for window size
85
+ """
86
+
87
+ self.framebuffer.resize()
88
+ self.ping_pong_buffer.resize()
89
+
90
+ def __del__(self) -> None:
91
+ """
92
+ Releases memory used by the frame
93
+ """
94
+
95
+ if self.vbo: self.vbo.release()
96
96
  if self.vao: self.vao.release()
@@ -1,289 +1,163 @@
1
- import numpy as np
2
- import moderngl as mgl
3
- from PIL import Image
4
- from ..render.shader import Shader
5
-
6
-
7
- class Framebuffer:
8
- engine: ...
9
- """Reference to the parent engine"""
10
- size: tuple[int] | None=None
11
- """The dimensions of the framebuffer (x, y). Defaults to window size if None"""
12
- scale: float=1.0
13
- """Scaling factor applied to the size. Best for use with default size"""
14
- texture_filter: tuple[int]=(mgl.NEAREST, mgl.NEAREST)
15
- """The filter applied to the texture when rendering"""
16
- fbo: mgl.Framebuffer=None
17
- """The core framebuffer the object provides abstraction for."""
18
- texture: mgl.Texture=None
19
- """The color texture of the framebuffer"""
20
- depth: mgl.Texture=None
21
- """The depth texture of the framebuffer"""
22
- _color_attachments = None
23
- """"""
24
- _depth_attachment = None
25
- """"""
26
-
27
- def __init__(self, engine: ..., size: tuple[int]=None, n_color_attachments: int=1, scale: float=1.0, linear_filter: bool=True) -> None:
28
- """
29
- Abstraction of the MGL framebuffer.
30
- Has the given number of color attachements (4-component) and 1 depth attachment.
31
- All textures are of uniform size.
32
- """
33
-
34
- self.engine = engine
35
- self.ctx = engine.ctx
36
- self._size = size
37
- self.scale = scale
38
- self.texture_filter = (mgl.LINEAR, mgl.LINEAR) if linear_filter else (mgl.NEAREST, mgl.NEAREST)
39
- self.attachments = n_color_attachments
40
-
41
- self.load_pipeline()
42
- self.generate_fbo()
43
-
44
- self.engine.fbos.append(self)
45
-
46
- def generate_fbo(self):
47
- """
48
- Generates fresh depth texture and color textures and creates an FBO
49
- """
50
-
51
- # Release existing memory
52
- self.__del__()
53
-
54
- # Create textures
55
- self._color_attachments = [self.ctx.texture(self.size, components=4)]
56
- for tex in self._color_attachments: tex.filter = self.texture_filter
57
- self._depth_attachment = self.ctx.depth_texture(self.size)
58
-
59
- # Create the internal fbo
60
- self.fbo = self.ctx.framebuffer(self._color_attachments, self._depth_attachment)
61
-
62
- def resize(self, new_size: tuple[int]=None) -> None:
63
- """
64
- Update set size framebuffers with the given size.
65
- """
66
-
67
- # Check that we are not updating the size to the existing size and
68
- if self._size and self._size == new_size: return
69
-
70
- # If we have a set size, update with the given size
71
- if self._size and new_size: self._size = new_size
72
-
73
- # Update the textures and fbo
74
- self.generate_fbo()
75
-
76
- def load_pipeline(self) -> None:
77
- """
78
- Loads the shader, vbo, and vao used to display the fbo
79
- """
80
-
81
- # Load Shaders
82
- self.shader = Shader(self.engine, self.engine.root + '/shaders/frame.vert', self.engine.root + '/shaders/frame.frag')
83
- self.engine.shader_handler.add(self.shader)
84
-
85
- # Load VAO
86
- self.vbo = self.ctx.buffer(np.array([[-1, -1, 0, 0, 0], [1, -1, 0, 1, 0], [1, 1, 0, 1, 1], [-1, 1, 0, 0, 1], [-1, -1, 0, 0, 0], [1, 1, 0, 1, 1]], dtype='f4'))
87
- self.vao = self.ctx.vertex_array(self.shader.program, [(self.vbo, '3f 2f', 'in_position', 'in_uv')], skip_errors=True)
88
-
89
- def render(self, render_target=None) -> None:
90
-
91
- target = render_target if render_target else self.engine.frame
92
-
93
- target.use()
94
- self.shader.program['screenTexture'] = 0
95
- self.texture.use(location=0)
96
- self.vao.render()
97
-
98
- def use(self) -> None:
99
- """
100
- Select this framebuffer for use
101
- """
102
-
103
- self.fbo.use()
104
-
105
- def clear(self) -> None:
106
- """
107
- Clear all data currently in the textures (set to black)
108
- """
109
-
110
- self.fbo.clear()
111
-
112
- def save(self, destination: str=None) -> None:
113
- """
114
- Saves the frame as an image to the given file destination
115
- """
116
-
117
- path = destination if destination else 'screenshot'
118
-
119
- data = self.fbo.read(components=3, alignment=1)
120
- img = Image.frombytes('RGB', self.size, data).transpose(Image.FLIP_TOP_BOTTOM)
121
- img.save(f'{path}.png')
122
-
123
-
124
- @property
125
- def size(self) -> tuple[int]:
126
- """Size of the textures in the fbo in pixels (x: int, y: int)"""
127
- size = self._size if self._size else self.engine.win_size
128
- size = tuple(map((lambda x: int(x * self.scale)), size))
129
- return size
130
- @property
131
- def texture(self) -> mgl.Texture:
132
- """First color attachment in the fbo"""
133
- return self._color_attachments[0]
134
- @property
135
- def color_attachments(self) -> ...:
136
- """List of all color attachments in the fbo"""
137
- return self._color_attachments
138
- @property
139
- def depth(self) -> mgl.Texture:
140
- """Depth attachment of the fbo"""
141
- return self._depth_attachment
142
- @property
143
- def data(self) -> bytes:
144
- """Reads the data from the fbo"""
145
- return self.fbo.read()
146
-
147
-
148
- @size.setter
149
- def size(self, value: tuple[int]=None) -> tuple[int]:
150
- self.resize(value)
151
- return self.size
152
-
153
- def __repr__(self) -> str:
154
- return f'<bsk.Framebuffer | size: {self.size}>'
155
-
156
- def __del__(self) -> None:
157
- """
158
- Releases all memory used by the fbo
159
- """
160
-
161
- if self._color_attachments: [tex.release() for tex in self._color_attachments]
162
- if self._depth_attachment: self._depth_attachment.release()
163
- if self.fbo: self.fbo.release()
164
-
165
- # class Framebuffer:
166
- # engine: ...
167
- # """Reference to the parent engine"""
168
- # fbo: mgl.Framebuffer = None
169
- # """The core framebuffer the object provides abstraction for."""
170
- # texture: mgl.Texture = None
171
- # """The color texture of the framebuffer"""
172
- # depth: mgl.Texture = None
173
- # """The depth texture of the framebuffer"""
174
- # size: tuple[int]
175
- # """The dimensions of the framebuffer (x, y)"""
176
-
177
- # def __init__(self, engine, size: tuple[int]=None, resolution_scale: float=1.0, components: int=4, filter=(mgl.LINEAR, mgl.LINEAR)) -> None:
178
- # """
179
- # Abstraction to the mgl framebuffer
180
- # Args:
181
- # engine: mgl.Engine:
182
- # The parent engine
183
- # size: tuple[int]:
184
- # The dimensions of the framebuffer (x, y)
185
- # """
186
-
187
- # # Set attributes
188
- # self.engine = engine
189
- # self.ctx = engine.ctx
190
- # self.components = components
191
-
192
- # # Set the size
193
- # self.resolution_scale = resolution_scale
194
- # self._size = size
195
- # self.size = self._size if self._size else self.engine.win_size
196
- # self.size = (int(self.size[0] * resolution_scale), int(self.size[1] * resolution_scale))
197
-
198
- # # Create the fbo
199
- # self.texture = self.ctx.texture(self.size, components=self.components)
200
- # self.depth = self.ctx.depth_texture(self.size)
201
- # self.fbo = self.ctx.framebuffer([self.texture], self.depth)
202
-
203
- # # Load Shaders
204
- # self.shader = Shader(self.engine, self.engine.root + '/shaders/frame.vert', self.engine.root + '/shaders/frame.frag')
205
- # self.engine.shader_handler.add(self.shader)
206
-
207
- # # Load VAO
208
- # self.vbo = self.ctx.buffer(np.array([[-1, -1, 0, 0, 0], [1, -1, 0, 1, 0], [1, 1, 0, 1, 1], [-1, 1, 0, 0, 1], [-1, -1, 0, 0, 0], [1, 1, 0, 1, 1]], dtype='f4'))
209
- # self.vao = self.ctx.vertex_array(self.shader.program, [(self.vbo, '3f 2f', 'in_position', 'in_uv')], skip_errors=True)
210
-
211
- # # Save the filter
212
- # self.filter = filter
213
-
214
- # # Add to the engine for updates
215
- # self.engine.fbos.append(self)
216
-
217
- # def render(self) -> None:
218
- # self.shader.program['screenTexture'] = 0
219
- # self.texture.use(location=0)
220
- # self.vao.render()
221
-
222
- # def use(self) -> None:
223
- # """
224
- # Select this framebuffer for use
225
- # """
226
-
227
- # self.fbo.use()
228
-
229
- # def clear(self) -> None:
230
- # """
231
- # Clear all data currently in the textures (set to black)
232
- # """
233
-
234
- # self.fbo.clear()
235
-
236
- # def save(self, destination: str=None) -> None:
237
- # """
238
- # Saves the frame as an image to the given file destination
239
- # """
240
-
241
- # path = destination if destination else 'screenshot'
242
-
243
- # data = self.fbo.read(components=3, alignment=1)
244
- # img = Image.frombytes('RGB', self.size, data).transpose(Image.FLIP_TOP_BOTTOM)
245
- # img.save(f'{path}.png')
246
-
247
- # def resize(self, size: tuple[int]=None) -> None:
248
- # """
249
- # Resize the buffer to the given size. None for window size
250
- # """
251
-
252
- # # Release old memory
253
- # self.__del__()
254
-
255
- # # Set/get size attribute
256
- # if size:
257
- # self.size = size
258
- # else:
259
- # self.size = self._size if self._size else self.engine.win_size
260
- # self.size = (int(self.size[0] * self.resolution_scale), int(self.size[1] * self.resolution_scale))
261
-
262
- # # Create the fbo
263
- # self.texture = self.ctx.texture(self.size, components=self.components)
264
- # self.depth = self.ctx.depth_texture(self.size)
265
- # self.fbo = self.ctx.framebuffer([self.texture], self.depth)
266
-
267
- # self.filter = self._filter
268
-
269
- # @property
270
- # def data(self):
271
- # return self.fbo.read(components=3, alignment=1)
272
-
273
- # @property
274
- # def filter(self):
275
- # return self.texture.filter
276
-
277
- # @filter.setter
278
- # def filter(self, value):
279
- # self._filter = value
280
- # self.texture.filter = value
281
-
282
- # def __repr__(self) -> str:
283
- # return f'<bsk.Framebuffer | size: {self.size}>'
284
-
285
- # def __del__(self) -> None:
286
- # # Release any existing memory in case of a resize
287
- # if self.texture: self.texture.release()
288
- # if self.depth: self.depth.release()
289
- # if self.fbo: self.fbo.release()
1
+ import numpy as np
2
+ import moderngl as mgl
3
+ from PIL import Image
4
+ from ..render.shader import Shader
5
+
6
+
7
+ class Framebuffer:
8
+ engine: ...
9
+ """Reference to the parent engine"""
10
+ size: tuple[int] | None=None
11
+ """The dimensions of the framebuffer (x, y). Defaults to window size if None"""
12
+ scale: float=1.0
13
+ """Scaling factor applied to the size. Best for use with default size"""
14
+ texture_filter: tuple[int]=(mgl.NEAREST, mgl.NEAREST)
15
+ """The filter applied to the texture when rendering"""
16
+ fbo: mgl.Framebuffer=None
17
+ """The core framebuffer the object provides abstraction for."""
18
+ texture: mgl.Texture=None
19
+ """The color texture of the framebuffer"""
20
+ depth: mgl.Texture=None
21
+ """The depth texture of the framebuffer"""
22
+ _color_attachments = None
23
+ """"""
24
+ _depth_attachment = None
25
+ """"""
26
+
27
+ def __init__(self, engine: ..., size: tuple[int]=None, n_color_attachments: int=1, scale: float=1.0, linear_filter: bool=True) -> None:
28
+ """
29
+ Abstraction of the MGL framebuffer.
30
+ Has the given number of color attachements (4-component) and 1 depth attachment.
31
+ All textures are of uniform size.
32
+ """
33
+
34
+ self.engine = engine
35
+ self.ctx = engine.ctx
36
+ self._size = size
37
+ self.scale = scale
38
+ self.texture_filter = (mgl.LINEAR, mgl.LINEAR) if linear_filter else (mgl.NEAREST, mgl.NEAREST)
39
+ self.n_attachments = n_color_attachments
40
+
41
+ self.load_pipeline()
42
+ self.generate_fbo()
43
+
44
+ self.engine.fbos.append(self)
45
+
46
+ def generate_fbo(self):
47
+ """
48
+ Generates fresh depth texture and color textures and creates an FBO
49
+ """
50
+
51
+ # Release existing memory
52
+ self.__del__()
53
+
54
+ # Create textures
55
+ self._color_attachments = [self.ctx.texture(self.size, components=4) for i in range(self.n_attachments)]
56
+ for tex in self._color_attachments: tex.filter = self.texture_filter
57
+ self._depth_attachment = self.ctx.depth_texture(self.size)
58
+
59
+ # Create the internal fbo
60
+ self.fbo = self.ctx.framebuffer(self._color_attachments, self._depth_attachment)
61
+
62
+ def resize(self, new_size: tuple[int]=None) -> None:
63
+ """
64
+ Update set size framebuffers with the given size.
65
+ """
66
+
67
+ # Check that we are not updating the size to the existing size and
68
+ if self._size and self._size == new_size: return
69
+
70
+ # If we have a set size, update with the given size
71
+ if self._size and new_size: self._size = new_size
72
+
73
+ # Update the textures and fbo
74
+ self.generate_fbo()
75
+
76
+ def load_pipeline(self) -> None:
77
+ """
78
+ Loads the shader, vbo, and vao used to display the fbo
79
+ """
80
+
81
+ # Load Shaders
82
+ self.shader = Shader(self.engine, self.engine.root + '/shaders/frame.vert', self.engine.root + '/shaders/frame.frag')
83
+ self.engine.shader_handler.add(self.shader)
84
+
85
+ # Load VAO
86
+ self.vbo = self.ctx.buffer(np.array([[-1, -1, 0, 0, 0], [1, -1, 0, 1, 0], [1, 1, 0, 1, 1], [-1, 1, 0, 0, 1], [-1, -1, 0, 0, 0], [1, 1, 0, 1, 1]], dtype='f4'))
87
+ self.vao = self.ctx.vertex_array(self.shader.program, [(self.vbo, '3f 2f', 'in_position', 'in_uv')], skip_errors=True)
88
+
89
+ def render(self, render_target=None) -> None:
90
+
91
+ target = render_target if render_target else self.engine.frame
92
+
93
+ target.use()
94
+ self.shader.program['screenTexture'] = 0
95
+ self.texture.use(location=0)
96
+ self.vao.render()
97
+
98
+ def use(self) -> None:
99
+ """
100
+ Select this framebuffer for use
101
+ """
102
+
103
+ self.fbo.use()
104
+
105
+ def clear(self) -> None:
106
+ """
107
+ Clear all data currently in the textures (set to black)
108
+ """
109
+
110
+ self.fbo.clear()
111
+
112
+ def save(self, destination: str=None) -> None:
113
+ """
114
+ Saves the frame as an image to the given file destination
115
+ """
116
+
117
+ path = destination if destination else 'screenshot'
118
+
119
+ data = self.fbo.read(components=3, alignment=1)
120
+ img = Image.frombytes('RGB', self.size, data).transpose(Image.FLIP_TOP_BOTTOM)
121
+ img.save(f'{path}.png')
122
+
123
+
124
+ @property
125
+ def size(self) -> tuple[int]:
126
+ """Size of the textures in the fbo in pixels (x: int, y: int)"""
127
+ size = self._size if self._size else self.engine.win_size
128
+ size = tuple(map((lambda x: int(x * self.scale)), size))
129
+ return size
130
+ @property
131
+ def texture(self) -> mgl.Texture:
132
+ """First color attachment in the fbo"""
133
+ return self._color_attachments[0]
134
+ @property
135
+ def color_attachments(self) -> list[mgl.Texture]:
136
+ """List of all color attachments in the fbo"""
137
+ return self._color_attachments
138
+ @property
139
+ def depth(self) -> mgl.Texture:
140
+ """Depth attachment of the fbo"""
141
+ return self._depth_attachment
142
+ @property
143
+ def data(self) -> bytes:
144
+ """Reads the data from the fbo"""
145
+ return self.fbo.read()
146
+
147
+
148
+ @size.setter
149
+ def size(self, value: tuple[int]=None) -> tuple[int]:
150
+ self.resize(value)
151
+ return self.size
152
+
153
+ def __repr__(self) -> str:
154
+ return f'<bsk.Framebuffer | size: {self.size}>'
155
+
156
+ def __del__(self) -> None:
157
+ """
158
+ Releases all memory used by the fbo
159
+ """
160
+
161
+ if self._color_attachments: [tex.release() for tex in self._color_attachments]
162
+ if self._depth_attachment: self._depth_attachment.release()
163
+ if self.fbo: self.fbo.release()