pyglet 2.1.dev4__py3-none-any.whl → 2.1.dev5__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.
pyglet/__init__.py CHANGED
@@ -15,7 +15,7 @@ if TYPE_CHECKING:
15
15
  from typing import Any, Callable, ItemsView, Sized
16
16
 
17
17
  #: The release version
18
- version = '2.1.dev4'
18
+ version = '2.1.dev5'
19
19
  __version__ = version
20
20
 
21
21
  MIN_PYTHON_VERSION = 3, 8
pyglet/__init__.pyi CHANGED
@@ -1,5 +1,6 @@
1
1
 
2
2
  from dataclasses import dataclass
3
+ from typing import Sequence
3
4
 
4
5
  from . import app as app
5
6
  from . import clock as clock
@@ -31,6 +32,36 @@ value: str
31
32
 
32
33
  @dataclass
33
34
  class Options:
34
- ...
35
+ audio: Sequence[str]
36
+ debug_font: bool
37
+ debug_gl: bool
38
+ debug_gl_trace: bool
39
+ debug_gl_trace_args: bool
40
+ debug_gl_shaders: bool
41
+ debug_graphics_batch: bool
42
+ debug_lib: bool
43
+ debug_media: bool
44
+ debug_texture: bool
45
+ debug_trace: bool
46
+ debug_trace_args: bool
47
+ debug_trace_depth: int
48
+ debug_trace_flush: bool
49
+ debug_win32: bool
50
+ debug_input: bool
51
+ debug_x11: bool
52
+ shadow_window: bool
53
+ vsync: bool | None
54
+ xsync: bool
55
+ xlib_fullscreen_override_redirect: bool
56
+ search_local_libs: bool
57
+ win32_gdi_font: bool
58
+ headless: bool
59
+ headless_device: int
60
+ win32_disable_shaping: bool
61
+ dw_legacy_naming: bool
62
+ win32_disable_xinput: bool
63
+ com_mta: bool
64
+ osx_alt_loop: bool
65
+ shader_bind_management: bool
35
66
 
36
67
  options: Options
pyglet/event.py CHANGED
@@ -152,7 +152,7 @@ class EventDispatcher:
152
152
  _event_stack: tuple | list = ()
153
153
 
154
154
  @classmethod
155
- def register_event_type(cls: Self, name: str) -> str:
155
+ def register_event_type(cls: type[Self], name: str) -> str:
156
156
  """Register an event type with the dispatcher.
157
157
 
158
158
  Before dispatching events, they must first be registered by name.
@@ -0,0 +1,520 @@
1
+ """Allows for a somewhat generic multitextured sprite.
2
+
3
+ This sprite behaves just like regular sprites.
4
+
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import pyglet
9
+ from pyglet.gl import glActiveTexture, GL_TEXTURE0, glBindTexture, glEnable, GL_BLEND, glBlendFunc, glDisable, \
10
+ glGetIntegerv, GLint, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_TRIANGLES, GL_MAX_TEXTURE_IMAGE_UNITS
11
+
12
+ class MultiTextureSpriteGroup(pyglet.graphics.Group):
13
+ """Shared Multi-texture Sprite rendering Group.
14
+
15
+ The Group defines custom ``__eq__`` and ``__hash__`` methods, and so will be
16
+ automatically coalesced with other MultiTexture Sprite Groups sharing the same
17
+ parent Group, Textures, and blend parameters.
18
+ """
19
+
20
+ def __init__(self, textures: dict[str, Texture], blend_src: int, blend_dest: int,
21
+ program: ShaderProgram | None = None, parent: Group | None = None) -> None:
22
+ """Create a sprite group for multiple textures and samplers.
23
+
24
+ All textures must share the same target type. The group is created internally
25
+ when a :py:class:`~pyglet.multitexture_sprite.MultiTextureSprite` is created;
26
+ applications usually do not need to explicitly create it.
27
+
28
+ Args:
29
+ textures:
30
+ A dictionary of textures, with the keys being the GLSL sampler name.
31
+ blend_src:
32
+ OpenGL blend source mode; for example, ``GL_SRC_ALPHA``.
33
+ blend_dest:
34
+ OpenGL blend destination mode; for example, ``GL_ONE_MINUS_SRC_ALPHA``.
35
+ program:
36
+ A custom ShaderProgram.
37
+ parent:
38
+ Optional parent group.
39
+ """
40
+ super().__init__(parent=parent)
41
+ self._textures = textures
42
+ self.blend_src = blend_src
43
+ self.blend_dest = blend_dest
44
+ self.program = program
45
+
46
+ def set_state(self) -> None:
47
+ """Called before this group is drawn to setup the shader state.
48
+
49
+ The shader program is activated than then all textures are bound and the
50
+ blend mode is setup.
51
+ """
52
+ self.program.use()
53
+
54
+ for idx, name in enumerate(self._textures):
55
+ self.program[name] = idx
56
+
57
+ for i, texture in enumerate(self._textures.values()):
58
+ glActiveTexture(GL_TEXTURE0 + i)
59
+ glBindTexture(texture.target, texture.id)
60
+
61
+ glEnable(GL_BLEND)
62
+ glBlendFunc(self.blend_src, self.blend_dest)
63
+
64
+ def unset_state(self) -> None:
65
+ """Called after all draw calls for the group have been made.
66
+
67
+ When done the blend mode is disabled, the shader program is deactivated,
68
+ and all textures are deactivated.
69
+ """
70
+ glDisable(GL_BLEND)
71
+ self.program.stop()
72
+ glActiveTexture(GL_TEXTURE0)
73
+
74
+ def __repr__(self) -> str:
75
+ return f'{self.__class__.__name__}({[(name,texture) for name,texture in self._textures.items()]})'
76
+
77
+ def __eq__(self, other: object | Group | MultiTextureSpriteGroup) -> bool:
78
+ """Determines if this group is the same as the other group.
79
+
80
+ The method is broken out like this to make it easier to follow and read.
81
+ """
82
+ if other.__class__ is not self.__class__:
83
+ return False
84
+
85
+ if len(self._textures) != len(other._textures):
86
+ return False
87
+
88
+ for name, texture in self._textures.items():
89
+ if name not in other._textures:
90
+ return False
91
+
92
+ if texture.id != other._textures[name].id or texture.target != other._textures[name].target:
93
+ return False
94
+
95
+ # Made it this far so just check the remainder
96
+ return (self.program is other.program and
97
+ self.blend_src == other.blend_src and
98
+ self.blend_dest == other.blend_dest)
99
+
100
+ def __hash__(self) -> int:
101
+ return hash((self.parent, self.program,
102
+ self.blend_src,
103
+ self.blend_dest) +
104
+ tuple([texture.id for texture in self._textures.values()]) +
105
+ tuple([texture.target for texture in self._textures.values()]))
106
+
107
+ # Allows the default shader to pick the appropriate sampler for the fragment shader
108
+ _SAMPLER_TYPES = {
109
+ pyglet.gl.GL_TEXTURE_2D: "sampler2D",
110
+ pyglet.gl.GL_TEXTURE_2D_ARRAY: "sampler2DArray"
111
+ }
112
+
113
+ # Allows the default shader to grab the correct coords based on texture type
114
+ _SAMPLER_COORDS = {
115
+ pyglet.gl.GL_TEXTURE_2D: ".xy",
116
+ pyglet.gl.GL_TEXTURE_2D_ARRAY: ""
117
+ }
118
+
119
+ def _get_default_mt_shader(images: dict[str, Texture]) -> ShaderProgram:
120
+ """Creates the default multi-texture shader based on the dict of textures passed in.
121
+
122
+ The default shader program will 'overlay' each texture layer on top of each other taking
123
+ into account of the alpha channel of each texture. Textures can be either normal
124
+ 2D textures or 2D texture arrays. The maximum number of textures you can layer is
125
+ determined by the maximum number of samplers you can have in a fragment shader.
126
+ """
127
+ max_tex = GLint()
128
+ glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, max_tex)
129
+ assert len(images) <= max_tex.value, f"The default multi-texture shader only supports up to a max of {max_tex.value} textures."
130
+
131
+ # Generate the default vertex shader
132
+ in_tex_coords = '\n'.join([f"in vec3 {name}_coords;" for name in images.keys()])
133
+ out_tex_coords = '\n'.join([f"out vec3 {name}_coords_frag;" for name in images.keys()])
134
+ tex_coords_assignments = '\n'.join([f"{name}_coords_frag = {name}_coords;" for name in images.keys()])
135
+
136
+ vertex_source = f"""
137
+ #version 150 core
138
+
139
+ in vec3 translate;
140
+ in vec4 colors;
141
+ in vec2 scale;
142
+ in vec3 position;
143
+ in float rotation;
144
+
145
+ {in_tex_coords}
146
+
147
+ out vec4 vertex_colors;
148
+
149
+ {out_tex_coords}
150
+
151
+ uniform WindowBlock {{
152
+ mat4 projection;
153
+ mat4 view;
154
+ }} window;
155
+ mat4 m_scale = mat4(1.0);
156
+ mat4 m_rotation = mat4(1.0);
157
+ mat4 m_translate = mat4(1.0);
158
+
159
+ void main()
160
+ {{
161
+ m_scale[0][0] = scale.x;
162
+ m_scale[1][1] = scale.y;
163
+ m_translate[3][0] = translate.x;
164
+ m_translate[3][1] = translate.y;
165
+ m_translate[3][2] = translate.z;
166
+ m_rotation[0][0] = cos(-radians(rotation));
167
+ m_rotation[0][1] = sin(-radians(rotation));
168
+ m_rotation[1][0] = -sin(-radians(rotation));
169
+ m_rotation[1][1] = cos(-radians(rotation));
170
+ gl_Position = window.projection * window.view * m_translate * m_rotation * m_scale * vec4(position, 1.0);
171
+ vertex_colors = colors;
172
+
173
+ {tex_coords_assignments}
174
+ }}
175
+ """
176
+
177
+ in_tex_coords = '\n'.join([f"in vec3 {name}_coords_frag;" for name in images.keys()])
178
+ uniform_samplers = '\n'.join([f"uniform {_SAMPLER_TYPES[tex.target]} {name};" for name,tex in images.items()])
179
+ tex_operations = '\n'.join([f" color = layer(texture({name}, {name}_coords_frag{_SAMPLER_COORDS[tex.target]}), color);" for name,tex in images.items()])
180
+ fragment_source = f"""
181
+ #version 150 core
182
+
183
+ in vec4 vertex_colors;
184
+ {in_tex_coords}
185
+
186
+ out vec4 final_colors;
187
+
188
+ {uniform_samplers}
189
+
190
+ vec4 layer(vec4 foreground, vec4 background) {{
191
+ return foreground * foreground.a + background * (1.0 - foreground.a);
192
+ }}
193
+
194
+ void main() {{
195
+ vec4 color = vec4(0.0, 0.0, 0.0, 1.0);
196
+ {tex_operations}
197
+ final_colors = color * vertex_colors;
198
+ }}
199
+ """
200
+
201
+ return pyglet.gl.current_context.create_program((vertex_source, 'vertex'), (fragment_source, 'fragment'))
202
+
203
+ class MultiTextureSprite(pyglet.sprite.Sprite):
204
+ """Creates a multi-textured sprite.
205
+
206
+ Multi-textured sprites behave just like regular sprites except they can
207
+ contain multiple texture layers. The default behavior is to overlay each
208
+ layer on top of each other. Each texture layer can be either a static image
209
+ or an animation. If the default behavior is not desired then a custom
210
+ shader program can be supplied overriding the default program.
211
+
212
+ The following complete example loads a 2 layer sprite and draws it to the
213
+ screen::
214
+
215
+ import pyglet
216
+
217
+ batch = pyglet.graphics.Batch()
218
+
219
+ logo_image = pyglet.image.load('logo.png')
220
+ kitten_image = pyglet.image.load('kitten.png')
221
+ sprite = pyglet.experimental.MultiTextureSprite({'kitten': kitten_image, 'logo': logo_image},
222
+ x=50, y=50, batch=batch)
223
+
224
+ window = pyglet.window.Window()
225
+
226
+ @window.event
227
+ def on_draw():
228
+ batch.draw()
229
+
230
+ pyglet.app.run()
231
+
232
+ If a custom program is provided then several assumptions are made by the
233
+ MultiTextureSprite class.
234
+
235
+ * The vertex shader is expected to have each texture coordinates passed in
236
+ with variables named ``<name>_coords`` as vec3 types where ``<name>`` is
237
+ the name of the layer given to the constructor.
238
+ * The fragment shader is expected to have samplers of the appropriate type
239
+ with variables named ``<name>`` where ``<name>`` is the name of the layer
240
+ given to the constructor.
241
+
242
+ """
243
+ group_class = MultiTextureSpriteGroup
244
+
245
+ def __init__(self,
246
+ images: dict[str, AbstractImage | Animation],
247
+ x: float = 0, y: float = 0, z: float = 0,
248
+ blend_src: int = GL_SRC_ALPHA,
249
+ blend_dest: int = GL_ONE_MINUS_SRC_ALPHA,
250
+ batch: Batch | None = None,
251
+ group: Group | None = None,
252
+ subpixel: bool = False,
253
+ program: ShaderProgram | None = None) -> None:
254
+ """Create a Sprite instance.
255
+
256
+ If the texture layers are different sizes then the largest texture by area is picked for
257
+ size of the sprite and then the other layers are scaled to that size.
258
+
259
+ Args:
260
+ images:
261
+ A dict object with the key being the name of the texture and the
262
+ value is either an Animation or AbstractImage. Currently
263
+ each item must be of the same size.
264
+ x:
265
+ X coordinate of the sprite.
266
+ y:
267
+ Y coordinate of the sprite.
268
+ z:
269
+ Z coordinate of the sprite.
270
+ blend_src:
271
+ OpenGL blend source mode. The default is suitable for
272
+ compositing sprites drawn from back-to-front.
273
+ blend_dest:
274
+ OpenGL blend destination mode. The default is suitable for
275
+ compositing sprites drawn from back-to-front.
276
+ batch:
277
+ Optional batch to add the sprite to.
278
+ group:
279
+ Optional parent group of the sprite.
280
+ subpixel:
281
+ Allow floating-point coordinates for the sprite. By default,
282
+ coordinates are restricted to integer values.
283
+
284
+ program:
285
+ A specific shader program to initialize the sprite with. The
286
+ default multi-texture overlay shader will be used if one is
287
+ not provided.
288
+ """
289
+ # Ensure the images are textures and load them up into a dict.
290
+ self._textures = {}
291
+ self._texture = None
292
+ for name, img in images.items():
293
+ if isinstance(img, pyglet.image.Animation):
294
+ # Grab the first frame
295
+ self._textures[name] = img.frames[0].image.get_texture()
296
+ else:
297
+ self._textures[name] = img.get_texture()
298
+
299
+ if not self._texture or (self._textures[name].height * self._textures[name].width) > (self._texture.height * self._texture.width):
300
+ self._texture = self._textures[name]
301
+
302
+ if not program:
303
+ self._program = _get_default_mt_shader(self._textures)
304
+ else:
305
+ self._program = program
306
+
307
+ # Right now don't call super unfortunently so we have to do everything ourselves
308
+ self._x = x
309
+ self._y = y
310
+ self._z = z
311
+
312
+ # Go through the images and find all animations
313
+ self._animations = {}
314
+ for name, img in images.items():
315
+ if isinstance(img, pyglet.image.Animation):
316
+ # Setup all of the animation things
317
+ # The key needs to match the key for self._textures so we change it out as needed
318
+ self._animations[name] = { "animation": img, "frame_idx": 0, "next_dt": img.frames[0].duration }
319
+ if img.frames[0].duration:
320
+ pyglet.clock.schedule_once(self._animate, self._animations[name]["next_dt"], name)
321
+
322
+ self._batch = batch
323
+ self._blend_src = blend_src
324
+ self._blend_dest = blend_dest
325
+ self._user_group = group
326
+ self._group = self.get_sprite_group()
327
+ self._subpixel = subpixel
328
+ # FIXME: This is to satisfy the main sprite code and should be done better
329
+ #self._texture = list(self._textures.values())[0].get_texture()
330
+ self._create_vertex_list()
331
+
332
+ def delete(self) -> None:
333
+ """Force immediate removal of the sprite from video memory.
334
+
335
+ This method removes any scheduled animation calls and then calls
336
+ pyglet.sprite.Sprite.delete method.
337
+ """
338
+ if self._animations:
339
+ pyglet.clock.unschedule(self._animate)
340
+
341
+ super().delete()
342
+
343
+ def get_sprite_group(self) -> MultiTextureSpriteGroup:
344
+ """Creates and returns a group to be used to render the sprite.
345
+
346
+ This is used internally to create a consolidated group for rendering.
347
+ """
348
+ return self.group_class(self._textures, self._blend_src, self._blend_dest, self._program, self._user_group)
349
+
350
+ def _animate(self, dt, key) -> None:
351
+ if key in self._animations:
352
+ animation = self._animations[key]
353
+ animation["frame_idx"] += 1
354
+ if animation["frame_idx"] >= len(animation["animation"].frames):
355
+ animation["frame_idx"] = 0
356
+ self.dispatch_event('on_animation_end')
357
+ if self._vertex_list is None:
358
+ return
359
+
360
+ frame = animation["animation"].frames[animation["frame_idx"]]
361
+
362
+ if frame.duration is not None:
363
+ duration = frame.duration - (animation["next_dt"] - dt)
364
+ duration = min(max(0, duration), frame.duration)
365
+ animation["next_dt"] = duration
366
+ self._set_multi_texture(key, frame.image.get_texture())
367
+ pyglet.clock.schedule_once(self._animate, animation["next_dt"], key)
368
+
369
+ def _set_multi_texture(self, key, new_tex: Texture) -> None:
370
+ if new_tex.id is not self._textures[key].id:
371
+ # Need to make a shallow copy to allow the batch object
372
+ # to correctly split this sprite from other sprite's groups.
373
+ # if not then you will be modifing all othe the other sprites
374
+ # textures dict object as well.
375
+ self._textures = self._textures.copy()
376
+ self._textures[key] = new_tex
377
+ self._vertex_list.delete()
378
+ self._group = self.get_sprite_group()
379
+ self._create_vertex_list()
380
+ else:
381
+ self._textures[key] = new_tex
382
+ getattr(self._vertex_list,f"{key}_coords")[:] = self._textures[key].tex_coords
383
+
384
+ def _create_vertex_list(self) -> None:
385
+ """
386
+ Override so we can send over texture coords for each texture being used.
387
+ """
388
+ tex_coords = {}
389
+ for name, tex in self._textures.items():
390
+ tex_coords[f"{name}_coords"] = ('f', tex.tex_coords)
391
+
392
+ self._vertex_list = self._program.vertex_list_indexed(
393
+ 4, GL_TRIANGLES, [0, 1, 2, 0, 2, 3], self._batch, self._group,
394
+ position=('f', self._get_vertices()),
395
+ colors=('Bn', (*self._rgb, int(self._opacity)) * 4),
396
+ translate=('f', (self._x, self._y, self._z) * 4),
397
+ scale=('f', (self._scale * self._scale_x, self._scale * self._scale_y) * 4),
398
+ rotation=('f', (self._rotation,) * 4),
399
+ **tex_coords)
400
+
401
+ def set_frame_index(self, name: str, frame_idx: int) -> None:
402
+ """Set the current Animation frame for the requested texture layer
403
+
404
+ If the texture layer isn't an animation then this method has no effect.
405
+
406
+ Args:
407
+ name:
408
+ The dict key given for the texture layer in the constructor
409
+ frame_idx:
410
+ The frame index to set for the given layer.
411
+ """
412
+ if name in self._animations:
413
+ animation = self._animations[name]
414
+ if frame_idx < len(animation["animation"].frames):
415
+ animation["frame_idx"] = frame_idx
416
+ frame = animation["animation"].frames[animation["frame_idx"]]
417
+ self._set_multi_texture(name, frame.image.get_texture())
418
+
419
+ def get_frame_index(self, name: str) -> int:
420
+ """Get the current Animation frame for the requested texture layer
421
+
422
+ If the texture layer isn't an animation then this method always returns 0.
423
+
424
+ Args:
425
+ name:
426
+ The dict key given for the texture layer in the constructor
427
+ """
428
+ if name in self._animations:
429
+ animation = self._animations[name]
430
+ return animation["frame_idx"]
431
+
432
+ return 0
433
+
434
+ def get_layer(self, name: str) -> AbstractImage | Animation | None:
435
+ """Return the requested layer. If it is not found then None is returned
436
+
437
+ Args:
438
+ name:
439
+ The dict key given for the texture layer in the constructor.
440
+ """
441
+ if name in self._animations:
442
+ return self._animations[name]
443
+ elif name in self._textures:
444
+ return self._textures[name]
445
+
446
+ return None
447
+
448
+ def set_layer(self, name: str, img: AbstractImage | Animation) -> None:
449
+ """Sets the layer to the new image or animation.
450
+
451
+ This method has no effect if name is not a valid layer. Note: if you
452
+ want to swap out a layer which is an animation then this will cause
453
+ all other animated layers for this sprite to pause until the swap is done.
454
+
455
+ Args:
456
+ name:
457
+ The dict key given for the texture layer in the constructor
458
+ """
459
+ if name in self._animations:
460
+ # Need to stop all animations temporarly so we can swap the layer out
461
+ pyglet.clock.unschedule(self._animate)
462
+ self._animations.pop(name)
463
+
464
+ # Grab the texture and replace what was there
465
+ tex = None
466
+ if isinstance(img, pyglet.image.Animation):
467
+ # Add the animation and schedule it based on pause
468
+ self._animations[name] = { "animation": img, "frame_idx": 0, "next_dt": img.frames[0].duration }
469
+ if img.frames[0].duration and not self._paused:
470
+ pyglet.clock.schedule_once(self._animate, self._animations[name]["next_dt"], name)
471
+
472
+ tex = img.frames[0].image.get_texture()
473
+ else:
474
+ tex = img.get_texture()
475
+
476
+ if tex:
477
+ self._set_multi_texture(name, tex)
478
+
479
+ if (tex.width * tex.height) > (self._texture.width * self._texture.height):
480
+ self._texture = tex
481
+ # Only update if we actually changed the "base" texture
482
+ self._update_position()
483
+
484
+
485
+ @property
486
+ def frame_index(self) -> None:
487
+ raise NotImplementedError("MultiTextureSprite does not support the frame_index property. Use get_frame_index instead.")
488
+
489
+ @frame_index.setter
490
+ def frame_index(self, index: int) -> None:
491
+ raise NotImplementedError("MultiTextureSprite does not support the frame_index property. Use set_frame_index instead.")
492
+
493
+ @property
494
+ def image(self) -> None:
495
+ raise NotImplementedError("MultiTextureSprite does not support the image property. Use get_layer instead.")
496
+
497
+ @image.setter
498
+ def image(self, img: AbstractImage | Animation) -> None:
499
+ raise NotImplementedError("MultiTextureSprite does not support the image property. Use set_layer instead.")
500
+
501
+ @property
502
+ def paused(self) -> bool:
503
+ return self._paused
504
+
505
+ @paused.setter
506
+ def paused(self, pause: bool) -> None:
507
+ if not hasattr(self, '_animations') or pause == self._paused:
508
+ return
509
+
510
+ self._paused = pause
511
+
512
+ if pause:
513
+ pyglet.clock.unschedule(self._animate)
514
+ else:
515
+ # Kick off all of the animations again
516
+ for name, animation in self._animations.items():
517
+ frame = animation["animation"].frames[animation["frame_idx"]]
518
+ if frame.duration:
519
+ animation["next_dt"] = frame.duration
520
+ pyglet.clock.schedule_once(self._animate, self._animations[name]["next_dt"], name)
@@ -201,6 +201,37 @@ _fragment_source: str = """#version 330 core
201
201
  }
202
202
  """
203
203
 
204
+ # Default blit source
205
+ _blit_vertex_source: str = """#version 330 core
206
+ in vec3 position;
207
+ in vec3 tex_coords;
208
+ out vec3 texture_coords;
209
+
210
+ uniform WindowBlock
211
+ {
212
+ mat4 projection;
213
+ mat4 view;
214
+ } window;
215
+
216
+ void main()
217
+ {
218
+ gl_Position = window.projection * window.view * vec4(position, 1.0);
219
+
220
+ texture_coords = tex_coords;
221
+ }
222
+ """
223
+
224
+ _blit_fragment_source: str = """#version 330 core
225
+ in vec3 texture_coords;
226
+ out vec4 final_colors;
227
+
228
+ uniform sampler2D our_texture;
229
+
230
+ void main()
231
+ {
232
+ final_colors = texture(our_texture, texture_coords.xy);
233
+ }
234
+ """
204
235
 
205
236
  def get_default_batch() -> Batch:
206
237
  """Batch used globally for objects that have no Batch specified."""
@@ -222,6 +253,16 @@ def get_default_shader() -> ShaderProgram:
222
253
  pyglet.gl.current_context.object_space.pyglet_graphics_default_shader = _shader_program
223
254
  return pyglet.gl.current_context.object_space.pyglet_graphics_default_shader
224
255
 
256
+ def get_default_blit_shader() -> ShaderProgram:
257
+ """A default basic shader for blitting, provides no blending."""
258
+ try:
259
+ return pyglet.gl.current_context.object_space.pyglet_graphics_default_blit_shader
260
+ except AttributeError:
261
+ _vertex_shader = shader.Shader(_blit_vertex_source, 'vertex')
262
+ _fragment_shader = shader.Shader(_blit_fragment_source, 'fragment')
263
+ _shader_program = shader.ShaderProgram(_vertex_shader, _fragment_shader)
264
+ pyglet.gl.current_context.object_space.pyglet_graphics_default_blit_shader = _shader_program
265
+ return pyglet.gl.current_context.object_space.pyglet_graphics_default_blit_shader
225
266
 
226
267
  _domain_class_map: dict[tuple[bool, bool], type[vertexdomain.VertexDomain]] = {
227
268
  # Indexed, Instanced : Domain
@@ -449,24 +490,24 @@ class Batch:
449
490
 
450
491
  def _dump_draw_list(self) -> None:
451
492
  def dump(group: Group, indent: str = '') -> None:
452
- print(indent, 'Begin group', group)
493
+ print(indent, 'Begin group', group)
453
494
  domain_map = self.group_map[group]
454
495
  for domain in domain_map.values():
455
- print(indent, ' ', domain)
496
+ print(indent, ' ', domain)
456
497
  for start, size in zip(*domain.allocator.get_allocated_regions()):
457
- print(indent, ' ', 'Region %d size %d:' % (start, size))
498
+ print(indent, ' ', 'Region %d size %d:' % (start, size))
458
499
  for key, attribute in domain.attribute_names.items():
459
- print(indent, ' ', end=' ')
500
+ print(indent, ' ', end=' ')
460
501
  try:
461
502
  region = attribute.get_region(attribute.buffer, start, size)
462
- print(key, region.array[:])
503
+ print(key, region.array[:])
463
504
  except: # noqa: E722
464
- print(key, '(unmappable)')
505
+ print(key, '(unmappable)')
465
506
  for child in self.group_children.get(group, ()):
466
507
  dump(child, indent + ' ')
467
- print(indent, 'End group', group)
508
+ print(indent, 'End group', group)
468
509
 
469
- print(f'Draw list for {self!r}:')
510
+ print(f'Draw list for {self!r}:')
470
511
  for group in self.top_groups:
471
512
  dump(group)
472
513