37 Commits

Author SHA1 Message Date
d39622c7e2 afegits els comandos de les tecles de funció 2026-03-27 23:32:44 +01:00
4910d201f9 primer comando implementat en la consola 2026-03-27 23:19:47 +01:00
e85800c5ed ja es pot escriure en la consola 2026-03-27 22:54:47 +01:00
f25ee18329 treballant en la consola 2026-03-27 22:24:55 +01:00
3712f0c8d9 implementat lanzcos en el supersampling 2026-03-27 21:59:14 +01:00
c063488e8e optimitzat textureToRenderer() 2026-03-27 20:34:45 +01:00
deb0a8677f mostra el render device en info_debug 2026-03-27 10:18:41 +01:00
c5a7c9e70d optimitzant textureToRenderer() 2026-03-27 09:46:25 +01:00
92453a6104 llevats colorins del debug 2026-03-26 08:49:49 +01:00
7aff3e2109 afegit overlay de debug 2026-03-26 08:47:24 +01:00
8d213e7b3e afinant els shaders 2026-03-26 07:46:11 +01:00
c6d409c303 corregides scanlines per a treballar amb subpixels per proporció 2026-03-25 22:31:34 +01:00
6914f7df93 supersampling ara aplica al tamany de la finestra, no a la textura base 2026-03-25 22:00:10 +01:00
1dbfff2c17 fix: el supersampling es feia per cpu en lloc de per gpu 2026-03-25 21:33:06 +01:00
3493636954 correccions en les traduccions 2026-03-25 20:51:52 +01:00
8ff1073e4a corregides les scanlines per a paletes amb fondo blanc 2026-03-25 18:31:36 +01:00
6497e26202 reordenades i renombrades les classes sprite 2026-03-25 18:01:33 +01:00
e0e37204d7 eliminada la classe Texture 2026-03-25 17:54:23 +01:00
6595b28790 clang-format
clang-tidy (macos)
2026-03-23 07:26:21 +01:00
0ddb6c85e1 afegit un poc de chroma al preset crt 2026-03-22 23:00:39 +01:00
f84007902e afegit flicker a postfx 2026-03-22 22:38:18 +01:00
49ae2ae41f per defecte el joc eixirà ara en valencià 2026-03-22 22:07:12 +01:00
c701421a8f corregits offsets en smb2.fnt 2026-03-22 22:04:23 +01:00
1ecb427106 supersampling implementat 2026-03-22 21:55:18 +01:00
c87779cc09 imlementant supersampling 2026-03-22 21:24:20 +01:00
24594fa89a deixant postfx al gust 2026-03-22 20:54:02 +01:00
030779794e F12 toggle de showFPS en mode debug 2026-03-22 19:59:48 +01:00
495c23a3d2 fix: en la migracio de la marquesina a la nova versio de Text s'havia posat per error un kerning superior al que havia
opt: millores en la getió de la marquesina per optimitzar rendiment
2026-03-22 19:43:35 +01:00
911ee7a13e modificada la paleta d'aseprite.gif per consistencia 2026-03-22 19:22:56 +01:00
b876ccbb09 afegit fallback a la font_gen
afegida deteccio de caracters no definits a font_gen
2026-03-22 19:19:00 +01:00
94684e8758 ferramenta de text pot importar gifs
ferramenta de text accepta separació entre quadricules de lletres
2026-03-22 19:06:01 +01:00
0c116665bc treballant en el generador de .fnt 2026-03-22 18:40:51 +01:00
d0ed49d192 revisada i actualitzada la classe Text per a donar suport a utf-8 2026-03-22 12:47:32 +01:00
5e013a8414 revisió de metodes de debug 2026-03-22 10:16:09 +01:00
0cdbeb768d revisió de la traducció al valencià 2026-03-22 09:57:25 +01:00
cd0477cc4c fix: nom de variable en Locale 2026-03-22 09:21:45 +01:00
c6e2779429 afegit suport multiidioma
afegida traducció al valencià
2026-03-22 09:00:51 +01:00
173 changed files with 5776 additions and 14143 deletions

View File

@@ -43,12 +43,14 @@ set(APP_SOURCES
source/core/rendering/pixel_reveal.cpp source/core/rendering/pixel_reveal.cpp
source/core/rendering/screen.cpp source/core/rendering/screen.cpp
source/core/rendering/surface.cpp source/core/rendering/surface.cpp
source/core/rendering/surface_animated_sprite.cpp source/core/rendering/sprite/animated_sprite.cpp
source/core/rendering/surface_dissolve_sprite.cpp source/core/rendering/sprite/dissolve_sprite.cpp
source/core/rendering/surface_moving_sprite.cpp source/core/rendering/sprite/moving_sprite.cpp
source/core/rendering/surface_sprite.cpp source/core/rendering/sprite/sprite.cpp
source/core/rendering/text.cpp source/core/rendering/text.cpp
source/core/rendering/texture.cpp
# Core - Locale
source/core/locale/locale.cpp
# Core - Resources # Core - Resources
source/core/resources/resource_list.cpp source/core/resources/resource_list.cpp
@@ -93,6 +95,7 @@ set(APP_SOURCES
source/game/scenes/title.cpp source/game/scenes/title.cpp
# Game - UI # Game - UI
source/game/ui/console.cpp
source/game/ui/notifier.cpp source/game/ui/notifier.cpp
# Utils # Utils
@@ -129,7 +132,11 @@ if(NOT APPLE)
if(GLSLC_EXE) if(GLSLC_EXE)
add_custom_command( add_custom_command(
OUTPUT "${SHADER_VERT_H}" "${SHADER_FRAG_H}" OUTPUT "${SHADER_VERT_H}" "${SHADER_FRAG_H}"
COMMAND "${CMAKE_SOURCE_DIR}/tools/shaders/compile_spirv.sh" COMMAND ${CMAKE_COMMAND}
-D GLSLC=${GLSLC_EXE}
-D SHADERS_DIR=${CMAKE_SOURCE_DIR}/data/shaders
-D HEADERS_DIR=${CMAKE_SOURCE_DIR}/source/core/rendering/sdl3gpu
-P ${CMAKE_SOURCE_DIR}/tools/shaders/compile_spirv.cmake
DEPENDS "${SHADER_VERT_SRC}" "${SHADER_FRAG_SRC}" DEPENDS "${SHADER_VERT_SRC}" "${SHADER_FRAG_SRC}"
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
COMMENT "Compilando shaders SPIR-V..." COMMENT "Compilando shaders SPIR-V..."

View File

@@ -7,23 +7,23 @@ assets:
- type: BITMAP - type: BITMAP
path: ${PREFIX}/data/font/smb2.gif path: ${PREFIX}/data/font/smb2.gif
- type: FONT - type: FONT
path: ${PREFIX}/data/font/smb2.txt path: ${PREFIX}/data/font/smb2.fnt
- type: BITMAP - type: BITMAP
path: ${PREFIX}/data/font/aseprite.gif path: ${PREFIX}/data/font/aseprite.gif
- type: FONT - type: FONT
path: ${PREFIX}/data/font/aseprite.txt path: ${PREFIX}/data/font/aseprite.fnt
- type: BITMAP - type: BITMAP
path: ${PREFIX}/data/font/gauntlet.gif path: ${PREFIX}/data/font/gauntlet.gif
- type: FONT - type: FONT
path: ${PREFIX}/data/font/gauntlet.txt path: ${PREFIX}/data/font/gauntlet.fnt
- type: BITMAP - type: BITMAP
path: ${PREFIX}/data/font/subatomic.gif path: ${PREFIX}/data/font/subatomic.gif
- type: FONT - type: FONT
path: ${PREFIX}/data/font/subatomic.txt path: ${PREFIX}/data/font/subatomic.fnt
- type: BITMAP - type: BITMAP
path: ${PREFIX}/data/font/8bithud.gif path: ${PREFIX}/data/font/8bithud.gif
- type: FONT - type: FONT
path: ${PREFIX}/data/font/8bithud.txt path: ${PREFIX}/data/font/8bithud.fnt
# PALETTES # PALETTES
palettes: palettes:
@@ -56,6 +56,13 @@ assets:
- type: PALETTE - type: PALETTE
path: ${PREFIX}/data/palette/steam-lords.pal path: ${PREFIX}/data/palette/steam-lords.pal
# LOCALE
locale:
- type: DATA
path: ${PREFIX}/data/locale/en.yaml
- type: DATA
path: ${PREFIX}/data/locale/ca.yaml
# INPUT # INPUT
input: input:
- type: DATA - type: DATA

132
data/font/8bithud.fnt Normal file
View File

@@ -0,0 +1,132 @@
# Font: 8bithud — generado desde 8-bit-hud.ttf size 5
# Generado con tools/font_gen/font_gen.py
box_width 8
box_height 8
columns 15
# codepoint_decimal ancho_visual
32 3 # U+0020
33 2 # !
34 5 # "
35 6 # #
36 6 # $
37 6 # %
38 6 # &
39 2 # '
40 3 # (
41 3 # )
42 4 # *
43 3 # +
44 2 # ,
45 3 # -
46 2 # .
47 4 # /
48 6 # 0
49 3 # 1
50 6 # 2
51 6 # 3
52 6 # 4
53 6 # 5
54 6 # 6
55 6 # 7
56 6 # 8
57 6 # 9
58 2 # :
59 2 # ;
60 4 # <
61 3 # =
62 4 # >
63 6 # ?
64 8 # @
65 6 # A
66 6 # B
67 6 # C
68 6 # D
69 6 # E
70 6 # F
71 6 # G
72 6 # H
73 6 # I
74 6 # J
75 6 # K
76 6 # L
77 6 # M
78 6 # N
79 6 # O
80 6 # P
81 6 # Q
82 6 # R
83 6 # S
84 6 # T
85 6 # U
86 5 # V
87 6 # W
88 6 # X
89 6 # Y
90 6 # Z
91 3 # [
92 4 # \
93 3 # ]
94 4 # ^
95 6 # _
96 2 # `
97 5 # a
98 5 # b
99 5 # c
100 5 # d
101 5 # e
102 5 # f
103 5 # g
104 5 # h
105 4 # i
106 5 # j
107 5 # k
108 5 # l
109 6 # m
110 5 # n
111 5 # o
112 5 # p
113 5 # q
114 5 # r
115 5 # s
116 4 # t
117 5 # u
118 5 # v
119 6 # w
120 4 # x
121 4 # y
122 5 # z
123 4 # {
124 1 # |
125 4 # }
126 4 # ~
192 6 # À
193 6 # Á
200 6 # È
201 6 # É
205 6 # Í
207 6 # Ï
210 6 # Ò
211 6 # Ó
218 6 # Ú
220 6 # Ü
209 6 # Ñ
199 6 # Ç
224 5 # à
225 5 # á
232 5 # è
233 5 # é
237 4 # í
239 4 # ï
242 5 # ò
243 5 # ó
250 5 # ú
252 5 # ü
241 5 # ñ
231 5 # ç
161 2 # ¡
191 6 # ¿
171 4 # «
187 4 # »
183 2 # ·

Binary file not shown.

Before

Width:  |  Height:  |  Size: 680 B

After

Width:  |  Height:  |  Size: 837 B

View File

@@ -1,194 +0,0 @@
# box width
8
# box height
8
# 32 espacio ( )
2
# 33 !
2
# 34 "
5
# 35 #
6
# 36 $
6
# 37 %
6
# 38 &
6
# 39 '
2
# 40 (
3
# 41 )
3
# 42 *
4
# 43 +
3
# 44 ,
2
# 45 -
3
# 46 .
2
# 47 /
4
# 48 0
6
# 49 1
6
# 50 2
6
# 51 3
6
# 52 4
6
# 53 5
6
# 54 6
6
# 55 7
6
# 56 8
6
# 57 9
6
# 58 :
2
# 59 ;
2
# 60 <
4
# 61 =
3
# 62 >
4
# 63 ?
6
# 64 @
8
# 65 A
6
# 66 B
6
# 67 C
6
# 68 D
6
# 69 E
6
# 70 F
6
# 71 G
6
# 72 H
6
# 73 I
6
# 74 J
6
# 75 K
6
# 76 L
6
# 77 M
6
# 78 N
6
# 79 O
6
# 80 P
6
# 81 Q
6
# 82 R
6
# 83 S
6
# 84 T
6
# 85 U
6
# 86 V
5
# 87 W
6
# 88 X
6
# 89 Y
6
# 90 Z
6
# 91 [
3
# 92 \
5
# 93 ]
3
# 94 ^
4
# 95 _
6
# 96 `
2
# 97 a
5
# 98 b
5
# 99 c
5
# 100 d
5
# 101 e
5
# 102 f
5
# 103 g
5
# 104 h
5
# 105 i
4
# 106 j
5
# 107 k
5
# 108 l
5
# 109 m
6
# 110 n
5
# 111 o
5
# 112 p
5
# 113 q
5
# 114 r
5
# 115 s
5
# 116 t
4
# 117 u
5
# 118 v
5
# 119 w
6
# 120 x
4
# 121 y
4
# 122 z
5
# 123 {
3
# 124 |
2
# 125 }
3
# 126 ~
3

134
data/font/aseprite.fnt Normal file
View File

@@ -0,0 +1,134 @@
# Font: aseprite — generado desde aseprite_font.gif
# Generado con tools/font_gen/font_gen.py
box_width 10
box_height 7
columns 16
cell_spacing 1
row_spacing 4
# codepoint_decimal ancho_visual
32 3 # U+0020
33 1 # !
34 3 # "
35 5 # #
36 4 # $
37 5 # %
38 5 # &
39 2 # '
40 2 # (
41 2 # )
42 5 # *
43 5 # +
44 2 # ,
45 3 # -
46 1 # .
47 3 # /
48 4 # 0
49 2 # 1
50 4 # 2
51 4 # 3
52 4 # 4
53 4 # 5
54 4 # 6
55 4 # 7
56 4 # 8
57 4 # 9
58 1 # :
59 2 # ;
60 3 # <
61 4 # =
62 3 # >
63 4 # ?
64 8 # @
65 4 # A
66 4 # B
67 4 # C
68 4 # D
69 4 # E
70 4 # F
71 4 # G
72 4 # H
73 1 # I
74 2 # J
75 4 # K
76 4 # L
77 5 # M
78 4 # N
79 5 # O
80 4 # P
81 5 # Q
82 4 # R
83 4 # S
84 5 # T
85 4 # U
86 5 # V
87 7 # W
88 5 # X
89 5 # Y
90 4 # Z
91 2 # [
92 3 # \
93 2 # ]
94 5 # ^
95 5 # _
96 3 # `
97 4 # a
98 4 # b
99 4 # c
100 4 # d
101 4 # e
102 2 # f
103 4 # g
104 4 # h
105 1 # i
106 2 # j
107 4 # k
108 1 # l
109 7 # m
110 4 # n
111 4 # o
112 4 # p
113 4 # q
114 3 # r
115 3 # s
116 2 # t
117 4 # u
118 4 # v
119 5 # w
120 5 # x
121 4 # y
122 4 # z
123 3 # {
124 1 # |
125 3 # }
126 4 # ~
192 5 # À
193 5 # Á
200 5 # È
201 5 # É
205 5 # Í
207 5 # Ï
210 5 # Ò
211 5 # Ó
218 5 # Ú
220 5 # Ü
209 5 # Ñ
199 5 # Ç
224 5 # à
225 5 # á
232 5 # è
233 5 # é
237 5 # í
239 5 # ï
242 5 # ò
243 5 # ó
250 5 # ú
252 5 # ü
241 5 # ñ
231 5 # ç
161 5 # ¡
191 5 # ¿
171 5 # «
187 5 # »
183 5 # ·

Binary file not shown.

Before

Width:  |  Height:  |  Size: 640 B

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -1,194 +0,0 @@
# box width
8
# box height
8
# 32 espacio ( )
3
# 33 !
1
# 34 "
3
# 35 #
3
# 36 $
4
# 37 %
5
# 38 &
5
# 39 '
2
# 40 (
2
# 41 )
2
# 42 *
5
# 43 +
5
# 44 ,
3
# 45 -
3
# 46 .
1
# 47 /
4
# 48 0
4
# 49 1
2
# 50 2
4
# 51 3
4
# 52 4
4
# 53 5
4
# 54 6
4
# 55 7
4
# 56 8
4
# 57 9
4
# 58 :
1
# 59 ;
1
# 60 <
3
# 61 =
4
# 62 >
4
# 63 ?
4
# 64 @
7
# 65 A
4
# 66 B
4
# 67 C
4
# 68 D
4
# 69 E
4
# 70 F
4
# 71 G
4
# 72 H
4
# 73 I
2
# 74 J
2
# 75 K
4
# 76 L
4
# 77 M
5
# 78 N
4
# 79 O
5
# 80 P
4
# 81 Q
5
# 82 R
4
# 83 S
4
# 84 T
5
# 85 U
4
# 86 V
5
# 87 W
7
# 88 X
5
# 89 Y
5
# 90 Z
4
# 91 [
2
# 92 \
3
# 93 ]
2
# 94 ^
5
# 95 _
5
# 96 `
3
# 97 a
4
# 98 b
4
# 99 c
4
# 100 d
4
# 101 e
4
# 102 f
2
# 103 g
4
# 104 h
4
# 105 i
1
# 106 j
2
# 107 k
4
# 108 l
1
# 109 m
7
# 110 n
4
# 111 o
4
# 112 p
4
# 113 q
4
# 114 r
3
# 115 s
3
# 116 t
2
# 117 u
4
# 118 v
4
# 119 w
5
# 120 x
5
# 121 y
4
# 122 z
4
# 123 {
3
# 124 |
3
# 125 }
3
# 126 ~
5

128
data/font/gauntlet.fnt Normal file
View File

@@ -0,0 +1,128 @@
# Font: gauntlet — generado desde Gauntlet.ttf size 7
# Generado con tools/font_gen/font_gen.py
box_width 8
box_height 8
columns 15
# codepoint_decimal ancho_visual
32 3 # U+0020
33 2 # !
34 5 # "
35 6 # #
36 6 # $
37 7 # %
38 7 # &
39 2 # '
40 4 # (
41 4 # )
42 6 # *
43 8 # +
44 2 # ,
45 7 # -
46 2 # .
47 7 # /
48 7 # 0
49 6 # 1
50 6 # 2
51 6 # 3
52 7 # 4
53 6 # 5
54 6 # 6
55 6 # 7
56 6 # 8
57 6 # 9
58 2 # :
59 3 # ;
60 5 # <
61 6 # =
62 5 # >
63 6 # ?
64 6 # @
65 6 # A
66 7 # B
67 7 # C
68 7 # D
69 7 # E
70 7 # F
71 7 # G
72 6 # H
73 6 # I
74 7 # J
75 7 # K
76 7 # L
77 7 # M
78 7 # N
79 7 # O
80 7 # P
81 7 # Q
82 7 # R
83 6 # S
84 6 # T
85 6 # U
86 6 # V
87 7 # W
88 7 # X
89 6 # Y
90 7 # Z
91 8 # [
92 3 # \
93 7 # ]
94 7 # ^
95 8 # _
97 6 # a
98 7 # b
99 7 # c
100 7 # d
101 7 # e
102 7 # f
103 7 # g
104 6 # h
105 6 # i
106 7 # j
107 7 # k
108 7 # l
109 7 # m
110 7 # n
111 7 # o
112 7 # p
113 7 # q
114 7 # r
115 6 # s
116 6 # t
117 6 # u
118 6 # v
119 7 # w
120 7 # x
121 6 # y
122 7 # z
126 6 # ~
192 6 # À
193 6 # Á
200 7 # È
201 7 # É
205 6 # Í
207 6 # Ï
210 7 # Ò
211 7 # Ó
218 6 # Ú
220 6 # Ü
209 7 # Ñ
199 7 # Ç
224 6 # à
225 6 # á
232 7 # è
233 7 # é
237 6 # í
239 6 # ï
242 7 # ò
243 7 # ó
250 6 # ú
252 6 # ü
241 7 # ñ
231 7 # ç
161 2 # ¡
191 6 # ¿
171 5 # «
187 5 # »
183 2 # ·

Binary file not shown.

Before

Width:  |  Height:  |  Size: 810 B

After

Width:  |  Height:  |  Size: 959 B

View File

@@ -1,194 +0,0 @@
# box width
8
# box height
8
# 32 espacio ( )
6
# 33 !
2
# 34 "
5
# 35 #
6
# 36 $
6
# 37 %
7
# 38 &
7
# 39 '
2
# 40 (
4
# 41 )
4
# 42 *
6
# 43 +
8
# 44 ,
2
# 45 -
7
# 46 .
2
# 47 /
7
# 48 0
7
# 49 1
6
# 50 2
6
# 51 3
6
# 52 4
7
# 53 5
6
# 54 6
6
# 55 7
6
# 56 8
6
# 57 9
6
# 58 :
2
# 59 ;
2
# 60 <
5
# 61 =
6
# 62 >
5
# 63 ?
6
# 64 @
6
# 65 A
6
# 66 B
7
# 67 C
7
# 68 D
7
# 69 E
7
# 70 F
7
# 71 G
7
# 72 H
6
# 73 I
6
# 74 J
7
# 75 K
7
# 76 L
7
# 77 M
7
# 78 N
7
# 79 O
7
# 80 P
7
# 81 Q
7
# 82 R
7
# 83 S
6
# 84 T
6
# 85 U
6
# 86 V
6
# 87 W
7
# 88 X
7
# 89 Y
6
# 90 Z
7
# 91 [
8
# 92 \
3
# 93 ]
7
# 94 ^
7
# 95 _
8
# 96 `
0
# 97 a
6
# 98 b
7
# 99 c
7
# 100 d
7
# 101 e
7
# 102 f
7
# 103 g
7
# 104 h
6
# 105 i
6
# 106 j
7
# 107 k
7
# 108 l
7
# 109 m
7
# 110 n
7
# 111 o
7
# 112 p
7
# 113 q
7
# 114 r
7
# 115 s
6
# 116 t
6
# 117 u
6
# 118 v
6
# 119 w
7
# 120 x
7
# 121 y
6
# 122 z
7
# 123 {
0
# 124 |
0
# 125 }
0
# 126 ~
0

132
data/font/smb2.fnt Normal file
View File

@@ -0,0 +1,132 @@
# Font: smb2 — generado desde Super Mario Bros. 2.ttf size 8
# Generado con tools/font_gen/font_gen.py
box_width 8
box_height 8
columns 15
# codepoint_decimal ancho_visual
32 7 # U+0020
33 7 # !
34 7 # "
35 7 # #
36 7 # $
37 7 # %
38 7 # &
39 7 # '
40 7 # (
41 7 # )
42 7 # *
43 7 # +
44 7 # ,
45 7 # -
46 7 # .
47 7 # /
48 7 # 0
49 7 # 1
50 7 # 2
51 7 # 3
52 7 # 4
53 7 # 5
54 7 # 6
55 7 # 7
56 7 # 8
57 7 # 9
58 7 # :
59 7 # ;
60 7 # <
61 7 # =
62 7 # >
63 7 # ?
64 7 # @
65 7 # A
66 7 # B
67 7 # C
68 7 # D
69 7 # E
70 7 # F
71 7 # G
72 7 # H
73 7 # I
74 7 # J
75 7 # K
76 7 # L
77 7 # M
78 7 # N
79 7 # O
80 7 # P
81 7 # Q
82 7 # R
83 7 # S
84 7 # T
85 7 # U
86 7 # V
87 7 # W
88 7 # X
89 7 # Y
90 7 # Z
91 7 # [
92 7 # \
93 7 # ]
94 7 # ^
95 7 # _
96 7 # `
97 7 # a
98 7 # b
99 7 # c
100 7 # d
101 7 # e
102 7 # f
103 7 # g
104 7 # h
105 7 # i
106 7 # j
107 7 # k
108 7 # l
109 7 # m
110 7 # n
111 7 # o
112 7 # p
113 7 # q
114 7 # r
115 7 # s
116 7 # t
117 7 # u
118 7 # v
119 7 # w
120 7 # x
121 7 # y
122 7 # z
123 7 # {
124 7 # |
125 7 # }
126 7 # ~
192 7 # À
193 7 # Á
200 7 # È
201 7 # É
205 7 # Í
207 7 # Ï
210 7 # Ò
211 7 # Ó
218 7 # Ú
220 7 # Ü
209 7 # Ñ
199 7 # Ç
224 7 # à
225 7 # á
232 7 # è
233 7 # é
237 7 # í
239 7 # ï
242 7 # ò
243 7 # ó
250 7 # ú
252 7 # ü
241 7 # ñ
231 7 # ç
161 7 # ¡
191 7 # ¿
171 7 # «
187 7 # »
183 7 # ·

Binary file not shown.

Before

Width:  |  Height:  |  Size: 798 B

After

Width:  |  Height:  |  Size: 968 B

View File

@@ -1,194 +0,0 @@
# box width
8
# box height
8
# 32 espacio ( )
7
# 33 !
7
# 34 "
7
# 35 #
7
# 36 $
7
# 37 %
7
# 38 &
7
# 39 '
7
# 40 (
7
# 41 )
7
# 42 *
7
# 43 +
7
# 44 ,
7
# 45 -
7
# 46 .
7
# 47 /
7
# 48 0
7
# 49 1
7
# 50 2
7
# 51 3
7
# 52 4
7
# 53 5
7
# 54 6
7
# 55 7
7
# 56 8
7
# 57 9
7
# 58 :
7
# 59 ;
7
# 60 <
7
# 61 =
7
# 62 >
7
# 63 ?
7
# 64 @
7
# 65 A
7
# 66 B
7
# 67 C
7
# 68 D
7
# 69 E
7
# 70 F
7
# 71 G
7
# 72 H
7
# 73 I
7
# 74 J
7
# 75 K
7
# 76 L
7
# 77 M
7
# 78 N
7
# 79 O
7
# 80 P
7
# 81 Q
7
# 82 R
7
# 83 S
7
# 84 T
7
# 85 U
7
# 86 V
7
# 87 W
7
# 88 X
7
# 89 Y
7
# 90 Z
7
# 91 [
7
# 92 \
7
# 93 ]
7
# 94 ^
7
# 95 _
7
# 96 `
7
# 97 a
7
# 98 b
7
# 99 c
7
# 100 d
7
# 101 e
7
# 102 f
7
# 103 g
7
# 104 h
7
# 105 i
7
# 106 j
7
# 107 k
7
# 108 l
7
# 109 m
7
# 110 n
7
# 111 o
7
# 112 p
7
# 113 q
7
# 114 r
7
# 115 s
7
# 116 t
7
# 117 u
7
# 118 v
7
# 119 w
7
# 120 x
7
# 121 y
7
# 122 z
7
# 123 {
7
# 124 |
7
# 125 }
7
# 126 ~
7

132
data/font/subatomic.fnt Normal file
View File

@@ -0,0 +1,132 @@
# Font: subatomic — generado desde atomics.TTF size 6
# Generado con tools/font_gen/font_gen.py
box_width 7
box_height 7
columns 15
# codepoint_decimal ancho_visual
32 4 # U+0020
33 1 # !
34 3 # "
35 5 # #
36 5 # $
37 5 # %
38 6 # &
39 1 # '
40 2 # (
41 2 # )
42 5 # *
43 5 # +
44 1 # ,
45 5 # -
46 1 # .
47 5 # /
48 5 # 0
49 2 # 1
50 5 # 2
51 5 # 3
52 5 # 4
53 5 # 5
54 5 # 6
55 5 # 7
56 5 # 8
57 5 # 9
58 1 # :
59 1 # ;
60 3 # <
61 5 # =
62 3 # >
63 4 # ?
64 5 # @
65 5 # A
66 5 # B
67 5 # C
68 5 # D
69 4 # E
70 5 # F
71 5 # G
72 5 # H
73 1 # I
74 5 # J
75 5 # K
76 4 # L
77 5 # M
78 5 # N
79 5 # O
80 5 # P
81 5 # Q
82 5 # R
83 5 # S
84 5 # T
85 5 # U
86 5 # V
87 5 # W
88 5 # X
89 5 # Y
90 5 # Z
91 2 # [
92 5 # \
93 2 # ]
94 3 # ^
95 5 # _
96 2 # `
97 4 # a
98 4 # b
99 3 # c
100 4 # d
101 4 # e
102 3 # f
103 4 # g
104 4 # h
105 1 # i
106 2 # j
107 3 # k
108 1 # l
109 5 # m
110 4 # n
111 4 # o
112 4 # p
113 4 # q
114 3 # r
115 4 # s
116 2 # t
117 4 # u
118 4 # v
119 5 # w
120 3 # x
121 4 # y
122 4 # z
123 3 # {
124 1 # |
125 3 # }
126 4 # ~
192 5 # À
193 5 # Á
200 4 # È
201 4 # É
205 1 # Í
207 1 # Ï
210 5 # Ò
211 5 # Ó
218 5 # Ú
220 5 # Ü
209 5 # Ñ
199 5 # Ç
224 4 # à
225 4 # á
232 4 # è
233 4 # é
237 1 # í
239 1 # ï
242 4 # ò
243 4 # ó
250 4 # ú
252 4 # ü
241 4 # ñ
231 3 # ç
161 1 # ¡
191 4 # ¿
171 3 # «
187 3 # »
183 1 # ·

Binary file not shown.

Before

Width:  |  Height:  |  Size: 540 B

After

Width:  |  Height:  |  Size: 648 B

View File

@@ -1,194 +0,0 @@
# box width
7
# box height
7
# 32 espacio ( )
4
# 33 !
1
# 34 "
3
# 35 #
5
# 36 $
5
# 37 %
5
# 38 &
6
# 39 '
1
# 40 (
2
# 41 )
2
# 42 *
5
# 43 +
5
# 44 ,
1
# 45 -
5
# 46 .
1
# 47 /
5
# 48 0
5
# 49 1
2
# 50 2
5
# 51 3
5
# 52 4
5
# 53 5
5
# 54 6
5
# 55 7
5
# 56 8
5
# 57 9
5
# 58 :
1
# 59 ;
2
# 60 <
3
# 61 =
5
# 62 >
3
# 63 ?
4
# 64 @
5
# 65 A
5
# 66 B
5
# 67 C
5
# 68 D
5
# 69 E
4
# 70 F
5
# 71 G
5
# 72 H
5
# 73 I
1
# 74 J
5
# 75 K
5
# 76 L
2
# 77 M
5
# 78 N
5
# 79 O
5
# 80 P
5
# 81 Q
5
# 82 R
5
# 83 S
5
# 84 T
5
# 85 U
5
# 86 V
5
# 87 W
5
# 88 X
5
# 89 Y
5
# 90 Z
5
# 91 [
2
# 92 \
5
# 93 ]
2
# 94 ^
3
# 95 _
5
# 96 `
2
# 97 a
4
# 98 b
4
# 99 c
3
# 100 d
4
# 101 e
4
# 102 f
3
# 103 g
4
# 104 h
4
# 105 i
1
# 106 j
2
# 107 k
3
# 108 l
1
# 109 m
5
# 110 n
4
# 111 o
4
# 112 p
4
# 113 q
4
# 114 r
3
# 115 s
4
# 116 t
2
# 117 u
4
# 118 v
4
# 119 w
5
# 120 x
3
# 121 y
4
# 122 z
4
# 123 {
0
# 124 |
0
# 125 }
0
# 126 ~
0

138
data/locale/ca.yaml Normal file
View File

@@ -0,0 +1,138 @@
# JailDoctor's Dilemma - Catalan Locale
# lang: ca
title:
marquee: "EI JAILERS!! ESTEM EN 2022 I ENCARA HO PETEM COM EN 1998!! QUÉ, HO HEU SENTIT O NO? ELS JAILGAMES HAN TORNAT!! SÍ, COLLONS, HAN TORNAT!! MÉS DE 10 TÍTOLS QUE EL JAILDOC TÉ A FOC LENT!! AIXÒ ÉS UNA BARBARITAT, PERÒ... QUIN EIXIRÀ PRIMER? I ATENCIÓ, QUE HI HA UN APARELLET NOU QUE VOS FARÀ VOLAR EL CAP: EL P.A.C.O.! PERÒ UN MOMENT... QUÈ ÉS AQUELLA COSETA QUE VE PER ALLÀ? OOOH, AQUELLA MINIASCII ÉS AMOR DEL BO!! LI PEGARIA UNA LLEPAETA A CADA BYTE! OSTRES! I NO VOS OBLIDEU DE PUJAR AQUELLS JAILGAMES VELLS I PANXUTS DE MS-DOS A GITHUB, QUE SI NO ES PERDRAN!! QUIN SERÀ EL PRÒXIM PROJECTE DE JAILDOC? SERÀ UN PROJECTE DE MERDA? AI MARE... NI IDEA, PERÒ ACÍ PODEU SABER-HO SI RESOLEU EL DILEMA DEL JAILDOCTOR... VOS ATREVIU O QUÈ? VAAAAA!!!"
menu:
play: "1. JUGAR"
keyboard: "2. REDEFINIR TECLES"
joystick: "3. REDEFINIR MANDO"
projects: "4. PROJECTES"
keys:
prompt0: "PREM UNA TECLA PER A ESQUERRA"
prompt1: "PREM UNA TECLA PER A DRETA"
prompt2: "PREM UNA TECLA PER A SALTAR"
defined: "TECLES DEFINIDES"
label0: "ESQUERRA: "
label1: "DRETA: "
label2: "SALTAR: "
invalid: "TECLA INVÀLIDA! PROVA'N UNA ALTRA"
already_used: "TECLA JA USADA! PROVA'N UNA ALTRA"
buttons:
prompt0: "PREM UN BOTÓ PER A ESQUERRA"
prompt1: "PREM UN BOTÓ PER A DRETA"
prompt2: "PREM UN BOTÓ PER A SALTAR"
defined: "BOTONS DEFINITS"
already_used: "BOTÓ JA USAT! PROVA'N UN ALTRE"
projects: "PROJECTES"
game_over:
title: "G A M E O V E R"
items: "OBJECTES: "
rooms: "SALES: "
worst_nightmare: "EL TEU PITJOR MALSON ÉS"
ending:
t0: "FINALMENT HO VA ACONSEGUIR"
t1: "ARRIBAR A LA JAIL"
t2: "AMB TOTS ELS SEUS PROJECTES"
t3: "A PUNT D'ALLIBERAR-LOS"
t4: "ALLÍ ESTAVEN TOTS ELS JAILERS"
t5: "ESPERANT QUE ELS JAILGAMES"
t6: "FOREN ALLIBERATS"
t7: "HI HAVIA FINS I TOT BARRULLS"
t8: "I BEGGINERS ENTRE LA GENT"
t9: "BRY ESTAVA PLORANT..."
t10: "PERÒ DE SOBTE ALGUNA COSA"
t11: "LI VA CRIDAR L'ATENCIÓ"
t12: "UN MUNT DE FERRALLA!"
t13: "PLE DE TRASTOS QUE NI ANAVEN!!"
t14: "I ALESHORES,"
t15: "QUARANTA PROJECTES NOUS"
t16: "VAN NÀIXER..."
ending2:
starring: "PROTAGONISTES"
jaildoctor: "JAILDOCTOR"
thank_you: "GRÀCIES"
for_playing: "PER JUGAR!"
credits:
instructions: "INSTRUCCIONS:"
l0: "AJUDA A JAILDOC A RECUPERAR"
l1: "ELS SEUS PROJECTES I ARRIBAR"
l2: "A LA JAIL PER ACABAR-LOS"
keys: "TECLES:"
keys_move: "CURSORS PER A MOURE I SALTAR"
f8: "F8 ACTIVAR/DESACTIVAR MÚSICA"
f11: "F11 PAUSAR EL JOC"
f1f2: "F1-F2 MIDA DE LA FINESTRA"
f3: "F3 PANTALLA COMPLETA"
f9: "F9 VORA DE LA PANTALLA"
author: "UN JOC DE JAILDESIGNER"
date: "FET A L'ESTIU/TARDOR DEL 2022"
love: "I LOVE JAILGAMES!"
achievements:
header: "ASSOLIMENT DESBLOQUEJAT!"
c1: "COSES BRILLANTS"
d1: "Aconseguiu el 25% dels objectes"
c2: "A MITJAN CAMÍ"
d2: "Aconseguiu el 50% dels objectes"
c3: "QUASI HI SOM"
d3: "Aconseguiu el 75% dels objectes"
c4: "EL COL·LECCIONISTA"
d4: "Aconseguiu el 100% dels objectes"
c5: "PASSEJANT PER ACÍ"
d5: "Visiteu 20 sales"
c6: "M'HE PERDUT"
d6: "Visiteu 40 sales"
c7: "M'AGRADA EXPLORAR"
d7: "Visiteu totes les sales"
c8: "JA ESTÀ?"
d8: "Completeu el joc"
c9: "UN FORAT EM VA ENGOLIR"
d9: "Completeu el joc sense entrar a la presó"
c10: "ELS MEUS PROJECTES"
d10: "Completeu el joc amb tots els objectes"
c11: "M'AGRADEN ELS MEUS AMICS DE COLORS"
d11: "Completeu el joc sense morir"
c12: "PROJECTES A CORRE-CUITA"
d12: "Completeu el joc en menys de 30 minuts"
ui:
press_again_menu: "PREM DE NOU PER TORNAR AL MENÚ"
press_again_exit: "PREM DE NOU PER EIXIR"
border_enabled: "VORA ACTIVADA"
border_disabled: "VORA DESACTIVADA"
fullscreen_enabled: "PANTALLA COMPLETA ACTIVADA"
fullscreen_disabled: "PANTALLA COMPLETA DESACTIVADA"
window_zoom: "ZOOM FINESTRA x"
postfx_enabled: "POSTFX ACTIVAT"
postfx_disabled: "POSTFX DESACTIVAT"
postfx: "POSTFX"
supersampling_enabled: "SUPERMOSTREIG ACTIVAT"
supersampling_disabled: "SUPERMOSTREIG DESACTIVAT"
palette: "PALETA"
integer_scale_enabled: "ESCALAT SENCER ACTIVAT"
integer_scale_disabled: "ESCALAT SENCER DESACTIVAT"
vsync_enabled: "V-SYNC ACTIVAT"
vsync_disabled: "V-SYNC DESACTIVAT"
scoreboard:
items: "TRESORS PILLATS "
time: " HORA "
rooms: "SALES"
game:
music_enabled: "MÚSICA ACTIVADA"
music_disabled: "MÚSICA DESACTIVADA"
paused: "JOC EN PAUSA"
running: "JOC EN MARXA"
enabled: " ACTIVAT"
disabled: " DESACTIVAT"
cheat_infinite_lives: "VIDES INFINITES"
cheat_invincible: "INVENCIBLE"
cheat_jail_open: "JAIL OBERTA"
debug_enabled: "DEBUG ACTIVAT"
debug_disabled: "DEBUG DESACTIVAT"

138
data/locale/en.yaml Normal file
View File

@@ -0,0 +1,138 @@
# JailDoctor's Dilemma - English Locale
# lang: en
title:
marquee: "HEY JAILERS!! IT'S 2022 AND WE'RE STILL ROCKING LIKE IT'S 1998!!! HAVE YOU HEARD IT? JAILGAMES ARE BACK!! YEEESSS BACK!! MORE THAN 10 TITLES ON JAILDOC'S KITCHEN!! THATS A LOOOOOOT OF JAILGAMES, BUT WHICH ONE WILL STRIKE FIRST? THERE IS ALSO A NEW DEVICE TO COME THAT WILL BLOW YOUR MIND WITH JAILGAMES ON THE GO: P.A.C.O. BUT WAIT! WHAT'S THAT BEAUTY I'M SEEING RIGHT OVER THERE?? OOOH THAT TINY MINIASCII IS PURE LOVE!! I WANT TO LICK EVERY BYTE OF IT!! OH SHIT! AND DON'T FORGET TO BRING BACK THOSE OLD AND FAT MS-DOS JAILGAMES TO GITHUB TO KEEP THEM ALIVE!! WHAT WILL BE THE NEXT JAILDOC RELEASE? WHAT WILL BE THE NEXT PROJECT TO COME ALIVE?? OH BABY WE DON'T KNOW BUT HERE YOU CAN FIND THE ANSWER, YOU JUST HAVE TO COMPLETE JAILDOCTOR'S DILEMMA ... COULD YOU?"
menu:
play: "1. PLAY"
keyboard: "2. REDEFINE KEYBOARD"
joystick: "3. REDEFINE JOYSTICK"
projects: "4. PROJECTS"
keys:
prompt0: "PRESS KEY FOR LEFT"
prompt1: "PRESS KEY FOR RIGHT"
prompt2: "PRESS KEY FOR JUMP"
defined: "KEYS DEFINED"
label0: "LEFT: "
label1: "RIGHT: "
label2: "JUMP: "
invalid: "INVALID KEY! TRY ANOTHER"
already_used: "KEY ALREADY USED! TRY ANOTHER"
buttons:
prompt0: "PRESS BUTTON FOR LEFT"
prompt1: "PRESS BUTTON FOR RIGHT"
prompt2: "PRESS BUTTON FOR JUMP"
defined: "BUTTONS DEFINED"
already_used: "BUTTON ALREADY USED! TRY ANOTHER"
projects: "PROJECTS"
game_over:
title: "G A M E O V E R"
items: "ITEMS: "
rooms: "ROOMS: "
worst_nightmare: "YOUR WORST NIGHTMARE IS"
ending:
t0: "HE FINALLY MANAGED"
t1: "TO GET TO THE JAIL"
t2: "WITH ALL HIS PROJECTS"
t3: "READY TO BE FREED"
t4: "ALL THE JAILERS WERE THERE"
t5: "WAITING FOR THE JAILGAMES"
t6: "TO BE RELEASED"
t7: "THERE WERE EVEN BARRULLS AND"
t8: "BEGINNERS AMONG THE CROWD"
t9: "BRY WAS CRYING..."
t10: "BUT SUDDENLY SOMETHING"
t11: "CAUGHT HIS ATTENTION"
t12: "A PILE OF JUNK!"
t13: "FULL OF NON WORKING TRASH!!"
t14: "AND THEN,"
t15: "FOURTY NEW PROJECTS"
t16: "WERE BORN..."
ending2:
starring: "STARRING"
jaildoctor: "JAILDOCTOR"
thank_you: "THANK YOU"
for_playing: "FOR PLAYING!"
credits:
instructions: "INSTRUCTIONS:"
l0: "HELP JAILDOC TO GET BACK ALL"
l1: "HIS PROJECTS AND GO TO THE"
l2: "JAIL TO FINISH THEM"
keys: "KEYS:"
keys_move: "CURSORS TO MOVE AND JUMP"
f8: "F8 TOGGLE THE MUSIC"
f11: "F11 PAUSE THE GAME"
f1f2: "F1-F2 WINDOWS SIZE"
f3: "F3 TOGGLE FULLSCREEN"
f9: "F9 TOOGLE BORDER SCREEN"
author: "A GAME BY JAILDESIGNER"
date: "MADE ON SUMMER/FALL 2022"
love: "I LOVE JAILGAMES! "
achievements:
header: "ACHIEVEMENT UNLOCKED!"
c1: "SHINY THINGS"
d1: "Get 25% of the items"
c2: "HALF THE WORK"
d2: "Get 50% of the items"
c3: "GETTING THERE"
d3: "Get 75% of the items"
c4: "THE COLLECTOR"
d4: "Get 100% of the items"
c5: "WANDERING AROUND"
d5: "Visit 20 rooms"
c6: "I GOT LOST"
d6: "Visit 40 rooms"
c7: "I LIKE TO EXPLORE"
d7: "Visit all rooms"
c8: "FINISH THE GAME"
d8: "Complete the game"
c9: "I WAS SUCKED BY A HOLE"
d9: "Complete the game without entering the jail"
c10: "MY LITTLE PROJECTS"
d10: "Complete the game with all items"
c11: "I LIKE MY MULTICOLOURED FRIENDS"
d11: "Complete the game without dying"
c12: "SHIT PROJECTS DONE FAST"
d12: "Complete the game in under 30 minutes"
ui:
press_again_menu: "PRESS AGAIN TO RETURN TO MENU"
press_again_exit: "PRESS AGAIN TO EXIT"
border_enabled: "BORDER ENABLED"
border_disabled: "BORDER DISABLED"
fullscreen_enabled: "FULLSCREEN ENABLED"
fullscreen_disabled: "FULLSCREEN DISABLED"
window_zoom: "WINDOW ZOOM x"
postfx_enabled: "POSTFX ENABLED"
postfx_disabled: "POSTFX DISABLED"
postfx: "POSTFX"
supersampling_enabled: "SUPERSAMPLING ON"
supersampling_disabled: "SUPERSAMPLING OFF"
palette: "PALETTE"
integer_scale_enabled: "INTEGER SCALE ENABLED"
integer_scale_disabled: "INTEGER SCALE DISABLED"
vsync_enabled: "V-SYNC ENABLED"
vsync_disabled: "V-SYNC DISABLED"
scoreboard:
items: "ITEMS COLLECTED "
time: " TIME "
rooms: "ROOMS"
game:
music_enabled: "MUSIC ENABLED"
music_disabled: "MUSIC DISABLED"
paused: "GAME PAUSED"
running: "GAME RUNNING"
enabled: " ENABLED"
disabled: " DISABLED"
cheat_infinite_lives: "INFINITE LIVES"
cheat_invincible: "INVINCIBLE"
cheat_jail_open: "JAIL IS OPEN"
debug_enabled: "DEBUG ENABLED"
debug_disabled: "DEBUG DISABLED"

View File

@@ -1,6 +1,7 @@
# THE JAIL # THE JAIL
room: room:
name: "THE JAIL" name_en: "THE JAIL"
name_ca: "LA JAIL"
bgColor: bright_blue bgColor: bright_blue
border: blue border: blue
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# ROAD TO THE JAIL # ROAD TO THE JAIL
room: room:
name: "ROAD TO THE JAIL" name_en: "ROAD TO THE JAIL"
name_ca: "CAMÍ A LA JAIL"
bgColor: black bgColor: black
border: blue border: blue
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# VOID MAIN # VOID MAIN
room: room:
name: "VOID MAIN" name_en: "VOID MAIN"
name_ca: "VOID MAIN"
bgColor: black bgColor: black
border: magenta border: magenta
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# JUMP THROUGH # JUMP THROUGH
room: room:
name: "JUMP THROUGH" name_en: "JUMP THROUGH"
name_ca: "SALTA A TRAVÉS"
bgColor: black bgColor: black
border: cyan border: cyan
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# BIG JUMP # BIG JUMP
room: room:
name: "BIG JUMP" name_en: "BIG JUMP"
name_ca: "EL GRAN BOT"
bgColor: black bgColor: black
border: red border: red
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# WELCOME TO MY ABBEY # WELCOME TO MY ABBEY
room: room:
name: "WELCOME TO MY ABBEY" name_en: "WELCOME TO MY ABBEY"
name_ca: "BENVINGUT A LA MEUA ABADIA"
bgColor: blue bgColor: blue
border: yellow border: yellow
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# SIGMASUA > TELEGRAM # SIGMASUA > TELEGRAM
room: room:
name: "SIGMASUA > TELEGRAM" name_en: "SIGMASUA > TELEGRAM"
name_ca: "SIGMASUA > TELEGRAM"
bgColor: black bgColor: black
border: blue border: blue
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# THE GARDEN # THE GARDEN
room: room:
name: "THE GARDEN" name_en: "THE GARDEN"
name_ca: "EL JARDÍ"
bgColor: black bgColor: black
border: cyan border: cyan
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# THE BIG TREE # THE BIG TREE
room: room:
name: "THE BIG TREE" name_en: "THE BIG TREE"
name_ca: "EL GRAN ARBRE"
bgColor: black bgColor: black
border: bright_blue border: bright_blue
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# TREE TOP # TREE TOP
room: room:
name: "TREE TOP" name_en: "TREE TOP"
name_ca: "DALT DE L'ARBRE"
bgColor: bright_black bgColor: bright_black
border: blue border: blue
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# LAZY ROOM # LAZY ROOM
room: room:
name: "LAZY ROOM" name_en: "LAZY ROOM"
name_ca: "LA SALA GOSSA"
bgColor: black bgColor: black
border: blue border: blue
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# THE PASSAGE # THE PASSAGE
room: room:
name: "THE PASSAGE" name_en: "THE PASSAGE"
name_ca: "EL PASSATGE"
bgColor: black bgColor: black
border: green border: green
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# TUNO KILLER # TUNO KILLER
room: room:
name: "TUNO KILLER" name_en: "TUNO KILLER"
name_ca: "MATATUNOS"
bgColor: black bgColor: black
border: blue border: blue
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# KILLING SPREE # KILLING SPREE
room: room:
name: "KILLING SPREE" name_en: "KILLING SPREE"
name_ca: "MATANÇA INDISCRIMINADA"
bgColor: black bgColor: black
border: blue border: blue
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# NOW THIS IS THE BATCAVE! # NOW THIS IS THE BATCAVE!
room: room:
name: "NOW THIS IS THE BATCAVE!" name_en: "NOW THIS IS THE BATCAVE!"
name_ca: "ESTA SI QUE ES LA BATCOVA!"
bgColor: black bgColor: black
border: black border: black
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# THE FRIDGE # THE FRIDGE
room: room:
name: "THE FRIDGE" name_en: "THE FRIDGE"
name_ca: "EL FRIGO"
bgColor: blue bgColor: blue
border: blue border: blue
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# I DID NOT COPY THIS ONE # I DID NOT COPY THIS ONE
room: room:
name: "I DID NOT COPY THIS ONE" name_en: "I DID NOT COPY THIS ONE"
name_ca: "ESTA NO LA HE COPIADA"
bgColor: black bgColor: black
border: magenta border: magenta
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# MAKE MONEY # MAKE MONEY
room: room:
name: "MAKE MONEY" name_en: "MAKE MONEY"
name_ca: "INHERITEDS!"
bgColor: black bgColor: black
border: yellow border: yellow
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# THIS CAN'T BE THE BATCAVE # THIS CAN'T BE THE BATCAVE
room: room:
name: "THIS CAN'T BE THE BATCAVE" name_en: "THIS CAN'T BE THE BATCAVE"
name_ca: "ESTA NO POT SER LA BATCOVA"
bgColor: black bgColor: black
border: cyan border: cyan
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# ENTRANCE TO THE VALLEY # ENTRANCE TO THE VALLEY
room: room:
name: "ENTRANCE TO THE VALLEY" name_en: "ENTRANCE TO THE VALLEY"
name_ca: "ENTRADA A LA VALL"
bgColor: black bgColor: black
border: red border: red
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# ENTER PAKU SIMBEL # ENTER PAKU SIMBEL
room: room:
name: "ENTER PAKU SIMBEL" name_en: "ENTER PAKU SIMBEL"
name_ca: "ACCEDINT A PAKU SIMBEL"
bgColor: bright_black bgColor: bright_black
border: yellow border: yellow
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# AEE REMAKE, PLEASE # AEE REMAKE, PLEASE
room: room:
name: "AEE REMAKE, PLEASE" name_en: "AEE REMAKE, PLEASE"
name_ca: "AEE REMAKE, PERFAPLIS"
bgColor: bright_black bgColor: bright_black
border: yellow border: yellow
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# INNER CHAMBER # INNER CHAMBER
room: room:
name: "INNER CHAMBER" name_en: "INNER CHAMBER"
name_ca: "CAMBRA INTERIOR"
bgColor: black bgColor: black
border: bright_yellow border: bright_yellow
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# PLAY IT AGAIN, SAM # PLAY IT AGAIN, SAM
room: room:
name: "PLAY IT AGAIN, SAM" name_en: "PLAY IT AGAIN, SAM"
name_ca: "TORNA-LA A TOCAR, SAM"
bgColor: black bgColor: black
border: bright_yellow border: bright_yellow
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# THE CHAPPEL # THE CHAPPEL
room: room:
name: "THE CHAPPEL" name_en: "THE CHAPPEL"
name_ca: "LA CAPELLA"
bgColor: blue bgColor: blue
border: yellow border: yellow
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# JINGLE BELLS # JINGLE BELLS
room: room:
name: "JINGLE BELLS" name_en: "JINGLE BELLS"
name_ca: "CAMPANETES"
bgColor: blue bgColor: blue
border: yellow border: yellow
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# THE BACKYARD # THE BACKYARD
room: room:
name: "THE BACKYARD" name_en: "THE BACKYARD"
name_ca: "EL PATI DE DARRERE"
bgColor: blue bgColor: blue
border: cyan border: cyan
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# YOU SHALL NOT PASS # YOU SHALL NOT PASS
room: room:
name: "YOU SHALL NOT PASS" name_en: "YOU SHALL NOT PASS"
name_ca: "NO PASSARÀS"
bgColor: bright_black bgColor: bright_black
border: black border: black
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# QUO VOIDIS # QUO VOIDIS
room: room:
name: "QUO VOIDIS" name_en: "QUO VOIDIS"
name_ca: "QUO VOIDIS"
bgColor: blue bgColor: blue
border: bright_black border: bright_black
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# QVOID IS A JAILGAME! # QVOID IS A JAILGAME!
room: room:
name: "QVOID IS A JAILGAME!" name_en: "QVOID IS A JAILGAME!"
name_ca: "QVOID ÉS UN JAILGAME!"
bgColor: blue bgColor: blue
border: bright_black border: bright_black
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# 256 COLORS # 256 COLORS
room: room:
name: "256 COLORS" name_en: "256 COLORS"
name_ca: "256 COLORS"
bgColor: black bgColor: black
border: bright_magenta border: bright_magenta
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# ...? # ...?
room: room:
name: "...?" name_en: "...?"
name_ca: "...?"
bgColor: black bgColor: black
border: cyan border: cyan
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# } WE ALL LOVE JAILGAMES } # } WE ALL LOVE JAILGAMES }
room: room:
name: "} WE ALL LOVE JAILGAMES }" name_en: "} WE ALL LOVE JAILGAMES }"
name_ca: "} AMOR PELS JAILGAMES }"
bgColor: black bgColor: black
border: bright_black border: bright_black
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# ULA HOP! # ULA HOP!
room: room:
name: "ULA HOP!" name_en: "ULA HOP!"
name_ca: "ULA HOP!"
bgColor: black bgColor: black
border: cyan border: cyan
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# SILICON BOOBS # SILICON BOOBS
room: room:
name: "SILICON BOOBS" name_en: "SILICON BOOBS"
name_ca: "MAMELLES DE SILICI"
bgColor: black bgColor: black
border: bright_green border: bright_green
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# BE CAREFUL WITH THE FUSE # BE CAREFUL WITH THE FUSE
room: room:
name: "BE CAREFUL WITH THE FUSE" name_en: "BE CAREFUL WITH THE FUSE"
name_ca: "COMPTE AMB EL FUSIBLE"
bgColor: black bgColor: black
border: bright_cyan border: bright_cyan
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# CHIP'N CHIP # CHIP'N CHIP
room: room:
name: "CHIP'N CHIP" name_en: "CHIP'N CHIP"
name_ca: "CHIP'N CHIP"
bgColor: black bgColor: black
border: bright_green border: bright_green
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# THE FINAL CROSSOVER # THE FINAL CROSSOVER
room: room:
name: "THE FINAL CROSSOVER" name_en: "THE FINAL CROSSOVER"
name_ca: "EL CROSSOVER DEFINITIU"
bgColor: bright_black bgColor: bright_black
border: yellow border: yellow
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# YOU'LL BELIEVE AROUNDER CAN FLY # YOU'LL BELIEVE AROUNDER CAN FLY
room: room:
name: "YOU'LL BELIEVE AROUNDER CAN FLY" name_en: "YOU'LL BELIEVE AROUNDER CAN FLY"
name_ca: "CREURÀS QUE ELS AROUNDERS VOLEN"
bgColor: black bgColor: black
border: cyan border: cyan
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# PREVENT THE CRISIS # PREVENT THE CRISIS
room: room:
name: "PREVENT THE CRISIS" name_en: "PREVENT THE CRISIS"
name_ca: "PREVÉ LA CRISI"
bgColor: black bgColor: black
border: bright_magenta border: bright_magenta
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# AROUND WITH ME # AROUND WITH ME
room: room:
name: "AROUND WITH ME" name_en: "AROUND WITH ME"
name_ca: "AROUNDA AMB MI"
bgColor: black bgColor: black
border: blue border: blue
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# P.A.C.O. ON THE GO # P.A.C.O. ON THE GO
room: room:
name: "P.A.C.O. ON THE GO" name_en: "P.A.C.O. ON THE GO"
name_ca: "P.A.C.O. EN MARXA"
bgColor: black bgColor: black
border: blue border: blue
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# THE TUBE # THE TUBE
room: room:
name: "THE TUBE" name_en: "THE TUBE"
name_ca: "EL TUB"
bgColor: black bgColor: black
border: blue border: blue
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# SANDWITCH AND COUNTER # SANDWITCH AND COUNTER
room: room:
name: "SANDWITCH AND COUNTER" name_en: "SANDWITCH AND COUNTER"
name_ca: "SANDVITX I COUNTER S."
bgColor: black bgColor: black
border: cyan border: cyan
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# FEEL THE PRESSURE # FEEL THE PRESSURE
room: room:
name: "FEEL THE PRESSURE" name_en: "FEEL THE PRESSURE"
name_ca: "NOTA LA PRESSIÓ"
bgColor: bright_black bgColor: bright_black
border: bright_yellow border: bright_yellow
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# FEEL THE HEAT # FEEL THE HEAT
room: room:
name: "FEEL THE HEAT" name_en: "FEEL THE HEAT"
name_ca: "NOTA EL CALORET"
bgColor: bright_black bgColor: bright_black
border: bright_yellow border: bright_yellow
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# THE BATTLE NEVER ENDS # THE BATTLE NEVER ENDS
room: room:
name: "THE BATTLE NEVER ENDS" name_en: "THE BATTLE NEVER ENDS"
name_ca: "LA BATALLA MAI ACABA"
bgColor: black bgColor: black
border: white border: white
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# WELCOME TO THE JAILBATTLE # WELCOME TO THE JAILBATTLE
room: room:
name: "WELCOME TO THE JAILBATTLE" name_en: "WELCOME TO THE JAILBATTLE"
name_ca: "BENVINGUTS A JAILBATTLE"
bgColor: green bgColor: green
border: bright_green border: bright_green
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# WE NEED A ROBOT # WE NEED A ROBOT
room: room:
name: "WE NEED A ROBOT" name_en: "WE NEED A JAILROBOT"
name_ca: "NECESSITEM UN JAILROBOT"
bgColor: black bgColor: black
border: red border: red
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# STORED JAILGAMES # STORED JAILGAMES
room: room:
name: "STORED JAILGAMES" name_en: "STORED JAILGAMES"
name_ca: "EL MAGATZEM DE JAILGAMES"
bgColor: black bgColor: black
border: blue border: blue
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# MINI ASCII # MINI ASCII
room: room:
name: "MINI ASCII" name_en: "MINI ASCII"
name_ca: "MINI ASCII"
bgColor: black bgColor: black
border: black border: black
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# BREAKOUT.LUA # BREAKOUT.LUA
room: room:
name: "BREAKOUT.LUA" name_en: "BREAKOUT.LUA"
name_ca: "BREAKOUT.LUA"
bgColor: black bgColor: black
border: black border: black
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# P.A.C.O. WORKSHOP # P.A.C.O. WORKSHOP
room: room:
name: "P.A.C.O. WORKSHOP" name_en: "P.A.C.O. WORKSHOP"
name_ca: "TALLER DE P.A.C.O."
bgColor: black bgColor: black
border: yellow border: yellow
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# THE BASEMENT # THE BASEMENT
room: room:
name: "THE BASEMENT" name_en: "THE BASEMENT"
name_ca: "EL SOTAN"
bgColor: black bgColor: black
border: blue border: blue
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# THAT'S A GUITAR # THAT'S A GUITAR
room: room:
name: "THAT'S A GUITAR" name_en: "THAT'S A GUITAR"
name_ca: "AIXÒ ÉS UNA GUITARRA"
bgColor: black bgColor: black
border: black border: black
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# HEAVY DEMONS ON LEGGINS # HEAVY DEMONS ON LEGGINS
room: room:
name: "HEAVY DEMONS ON LEGGINS" name_en: "HEAVY DEMONS ON LEGGINS"
name_ca: "DIMONIS HEAVIES AMB MALLES"
bgColor: black bgColor: black
border: black border: black
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# JAILGAMES GO TO HELL # JAILGAMES GO TO HELL
room: room:
name: "JAILGAMES GO TO HELL" name_en: "JAILGAMES GO TO HELL"
name_ca: "JAILGAMES A L'INFERN"
bgColor: red bgColor: red
border: bright_red border: bright_red
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# CHIRPING # CHIRPING
room: room:
name: "CHIRPING" name_en: "CHIRPING"
name_ca: "CHIRPING"
bgColor: black bgColor: black
border: magenta border: magenta
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# STATIC # STATIC
room: room:
name: "STATIC" name_en: "STATIC"
name_ca: "ESTÀTICA"
bgColor: black bgColor: black
border: bright_magenta border: bright_magenta
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -1,6 +1,7 @@
# MAGNETIC FIELDS # MAGNETIC FIELDS
room: room:
name: "MAGNETIC FIELDS" name_en: "MAGNETIC FIELDS"
name_ca: "CAMPS MAGNÈTICS"
bgColor: black bgColor: black
border: bright_red border: bright_red
tileSetFile: standard.gif tileSetFile: standard.gif

View File

@@ -0,0 +1,48 @@
#version 450
layout(location = 0) in vec2 v_uv;
layout(location = 0) out vec4 out_color;
layout(set = 2, binding = 0) uniform sampler2D source;
layout(set = 3, binding = 0) uniform DownscaleUniforms {
int algorithm; // 0 = Lanczos2 (ventana 2, ±2 taps), 1 = Lanczos3 (ventana 3, ±3 taps)
float pad0;
float pad1;
float pad2;
} u;
// Kernel Lanczos normalizado: sinc(t) * sinc(t/a) para |t| < a, 0 fuera.
float lanczos(float t, float a) {
t = abs(t);
if (t < 0.0001) { return 1.0; }
if (t >= a) { return 0.0; }
const float PI = 3.14159265358979;
float pt = PI * t;
return (a * sin(pt) * sin(pt / a)) / (pt * pt);
}
void main() {
vec2 src_size = vec2(textureSize(source, 0));
// Posición en coordenadas de texel (centros de texel en N+0.5)
vec2 p = v_uv * src_size;
vec2 p_floor = floor(p);
float a = (u.algorithm == 0) ? 2.0 : 3.0;
int win = int(a);
vec4 color = vec4(0.0);
float weight_sum = 0.0;
for (int j = -win; j <= win; j++) {
for (int i = -win; i <= win; i++) {
// Centro del texel (i,j) relativo a p_floor
vec2 tap_center = p_floor + vec2(float(i), float(j)) + 0.5;
vec2 offset = tap_center - p;
float w = lanczos(offset.x, a) * lanczos(offset.y, a);
color += texture(source, tap_center / src_size) * w;
weight_sum += w;
}
}
out_color = (weight_sum > 0.0) ? (color / weight_sum) : vec4(0.0, 0.0, 0.0, 1.0);
}

View File

@@ -22,6 +22,10 @@ layout(set = 3, binding = 0) uniform PostFXUniforms {
float gamma_strength; float gamma_strength;
float curvature; float curvature;
float bleeding; float bleeding;
float pixel_scale; // physical pixels per logical pixel (vh / tex_height_)
float time; // seconds since SDL init
float oversample; // supersampling factor (1.0 = off, 3.0 = 3×SS)
float flicker; // 0 = off, 1 = phosphor flicker ~50 Hz — 48 bytes total (3 × 16)
} u; } u;
// YCbCr helpers for NTSC bleeding // YCbCr helpers for NTSC bleeding
@@ -64,23 +68,25 @@ void main() {
// Muestra base // Muestra base
vec3 base = texture(scene, uv).rgb; vec3 base = texture(scene, uv).rgb;
// Sangrado NTSC — difuminado horizontal de crominancia // Sangrado NTSC — difuminado horizontal de crominancia.
// step = 1 pixel lógico de juego en UV (corrige SS: textureSize.x = game_w * oversample).
vec3 colour; vec3 colour;
if (u.bleeding > 0.0) { if (u.bleeding > 0.0) {
float tw = float(textureSize(scene, 0).x); float tw = float(textureSize(scene, 0).x);
float step = u.oversample / tw; // 1 pixel lógico en UV
vec3 ycc = rgb_to_ycc(base); vec3 ycc = rgb_to_ycc(base);
vec3 ycc_l2 = rgb_to_ycc(texture(scene, uv - vec2(2.0/tw, 0.0)).rgb); vec3 ycc_l2 = rgb_to_ycc(texture(scene, uv - vec2(2.0*step, 0.0)).rgb);
vec3 ycc_l1 = rgb_to_ycc(texture(scene, uv - vec2(1.0/tw, 0.0)).rgb); vec3 ycc_l1 = rgb_to_ycc(texture(scene, uv - vec2(1.0*step, 0.0)).rgb);
vec3 ycc_r1 = rgb_to_ycc(texture(scene, uv + vec2(1.0/tw, 0.0)).rgb); vec3 ycc_r1 = rgb_to_ycc(texture(scene, uv + vec2(1.0*step, 0.0)).rgb);
vec3 ycc_r2 = rgb_to_ycc(texture(scene, uv + vec2(2.0/tw, 0.0)).rgb); vec3 ycc_r2 = rgb_to_ycc(texture(scene, uv + vec2(2.0*step, 0.0)).rgb);
ycc.yz = (ycc_l2.yz + ycc_l1.yz*2.0 + ycc.yz*2.0 + ycc_r1.yz*2.0 + ycc_r2.yz) / 8.0; ycc.yz = (ycc_l2.yz + ycc_l1.yz*2.0 + ycc.yz*2.0 + ycc_r1.yz*2.0 + ycc_r2.yz) / 8.0;
colour = mix(base, ycc_to_rgb(ycc), u.bleeding); colour = mix(base, ycc_to_rgb(ycc), u.bleeding);
} else { } else {
colour = base; colour = base;
} }
// Aberración cromática // Aberración cromática (drift animado con time para efecto NTSC real)
float ca = u.chroma_strength * 0.005; float ca = u.chroma_strength * 0.005 * (1.0 + 0.15 * sin(u.time * 7.3));
colour.r = texture(scene, uv + vec2(ca, 0.0)).r; colour.r = texture(scene, uv + vec2(ca, 0.0)).r;
colour.b = texture(scene, uv - vec2(ca, 0.0)).b; colour.b = texture(scene, uv - vec2(ca, 0.0)).b;
@@ -90,14 +96,24 @@ void main() {
colour = mix(colour, lin, u.gamma_strength); colour = mix(colour, lin, u.gamma_strength);
} }
// Scanlines // Scanlines — proporción 2/3 brillantes + 1/3 oscuras por fila lógica.
float texHeight = float(textureSize(scene, 0).y); // Casos especiales: 1 subfila → sin efecto; 2 subfilas → 1+1 (50/50).
float scaleY = u.screen_height / texHeight; // Constantes ajustables:
float screenY = uv.y * u.screen_height; const float SCAN_DARK_RATIO = 0.333; // fracción de subfilas oscuras (ps >= 3)
float posInRow = mod(screenY, scaleY); const float SCAN_DARK_FLOOR = 0.42; // multiplicador de brillo de subfilas oscuras
float scanLineDY = posInRow / scaleY - 0.5; if (u.scanline_strength > 0.0) {
float scan = max(1.0 - scanLineDY * scanLineDY * 6.0, 0.12) * 3.5; float ps = max(1.0, round(u.pixel_scale));
colour *= mix(1.0, scan, u.scanline_strength); float frac_in_row = fract(uv.y * u.screen_height);
float row_pos = floor(frac_in_row * ps);
// bright_rows: cuántas subfilas son brillantes
// ps==1 → ps (todo brillante → is_dark nunca se activa)
// ps==2 → 1 brillante + 1 oscura
// ps>=3 → floor(ps * (1 - DARK_RATIO)) brillantes
float bright_rows = (ps < 2.0) ? ps : ((ps < 3.0) ? 1.0 : floor(ps * (1.0 - SCAN_DARK_RATIO)));
float is_dark = step(bright_rows, row_pos);
float scan = mix(1.0, SCAN_DARK_FLOOR, is_dark);
colour *= mix(1.0, scan, u.scanline_strength);
}
if (u.gamma_strength > 0.0) { if (u.gamma_strength > 0.0) {
vec3 enc = pow(colour, vec3(1.0 / 2.2)); vec3 enc = pow(colour, vec3(1.0 / 2.2));
@@ -109,7 +125,8 @@ void main() {
float vignette = 1.0 - dot(d, d) * u.vignette_strength; float vignette = 1.0 - dot(d, d) * u.vignette_strength;
colour *= clamp(vignette, 0.0, 1.0); colour *= clamp(vignette, 0.0, 1.0);
// Máscara de fósforo RGB // Máscara de fósforo RGB — después de scanlines (orden original):
// filas brillantes saturadas → máscara invisible, filas oscuras → RGB visible.
if (u.mask_strength > 0.0) { if (u.mask_strength > 0.0) {
float whichMask = fract(gl_FragCoord.x * 0.3333333); float whichMask = fract(gl_FragCoord.x * 0.3333333);
vec3 mask = vec3(0.80); vec3 mask = vec3(0.80);
@@ -122,5 +139,11 @@ void main() {
colour = mix(colour, colour * mask, u.mask_strength); colour = mix(colour, colour * mask, u.mask_strength);
} }
// Parpadeo de fósforo CRT (~50 Hz)
if (u.flicker > 0.0) {
float flicker_wave = sin(u.time * 100.0) * 0.5 + 0.5;
colour *= 1.0 - u.flicker * 0.04 * flicker_wave;
}
out_color = vec4(colour, 1.0); out_color = vec4(colour, 1.0);
} }

Binary file not shown.

15
data/shaders/upscale.frag Normal file
View File

@@ -0,0 +1,15 @@
#version 450
// Vulkan GLSL fragment shader — Nearest-neighbour upscale pass
// Used as the first render pass when supersampling is active.
// Compile: glslc upscale.frag -o upscale.frag.spv
// xxd -i upscale.frag.spv > ../../source/core/rendering/sdl3gpu/upscale_frag_spv.h
layout(location = 0) in vec2 v_uv;
layout(location = 0) out vec4 out_color;
layout(set = 2, binding = 0) uniform sampler2D scene;
void main() {
out_color = texture(scene, v_uv);
}

Binary file not shown.

View File

@@ -41,7 +41,7 @@ void Audio::update() {
} }
// Reproduce la música // Reproduce la música
void Audio::playMusic(const std::string& name, const int loop) { void Audio::playMusic(const std::string& name, const int loop) { // NOLINT(readability-convert-member-functions-to-static)
bool new_loop = (loop != 0); bool new_loop = (loop != 0);
// Si ya está sonando exactamente la misma pista y mismo modo loop, no hacemos nada // Si ya está sonando exactamente la misma pista y mismo modo loop, no hacemos nada
@@ -71,7 +71,7 @@ void Audio::playMusic(const std::string& name, const int loop) {
} }
// Pausa la música // Pausa la música
void Audio::pauseMusic() { void Audio::pauseMusic() { // NOLINT(readability-convert-member-functions-to-static)
if (music_enabled_ && music_.state == MusicState::PLAYING) { if (music_enabled_ && music_.state == MusicState::PLAYING) {
JA_PauseMusic(); JA_PauseMusic();
music_.state = MusicState::PAUSED; music_.state = MusicState::PAUSED;
@@ -79,7 +79,7 @@ void Audio::pauseMusic() {
} }
// Continua la música pausada // Continua la música pausada
void Audio::resumeMusic() { void Audio::resumeMusic() { // NOLINT(readability-convert-member-functions-to-static)
if (music_enabled_ && music_.state == MusicState::PAUSED) { if (music_enabled_ && music_.state == MusicState::PAUSED) {
JA_ResumeMusic(); JA_ResumeMusic();
music_.state = MusicState::PLAYING; music_.state = MusicState::PLAYING;
@@ -87,7 +87,7 @@ void Audio::resumeMusic() {
} }
// Detiene la música // Detiene la música
void Audio::stopMusic() { void Audio::stopMusic() { // NOLINT(readability-make-member-function-const)
if (music_enabled_) { if (music_enabled_) {
JA_StopMusic(); JA_StopMusic();
music_.state = MusicState::STOPPED; music_.state = MusicState::STOPPED;

View File

@@ -6,16 +6,14 @@
#include <vector> // Para vector #include <vector> // Para vector
#include "core/input/input.hpp" // Para Input, InputAction, Input::DO_NOT_ALLOW_REPEAT #include "core/input/input.hpp" // Para Input, InputAction, Input::DO_NOT_ALLOW_REPEAT
#include "core/locale/locale.hpp" // Para Locale
#include "core/rendering/screen.hpp" // Para Screen #include "core/rendering/screen.hpp" // Para Screen
#include "game/options.hpp" // Para Options, options, OptionsVideo, Section #include "game/options.hpp" // Para Options, options, OptionsVideo, Section
#include "game/scene_manager.hpp" // Para SceneManager #include "game/scene_manager.hpp" // Para SceneManager
#include "game/ui/console.hpp" // Para Console
#include "game/ui/notifier.hpp" // Para Notifier, NotificationText #include "game/ui/notifier.hpp" // Para Notifier, NotificationText
#include "utils/utils.hpp" // Para stringInVector #include "utils/utils.hpp" // Para stringInVector
#ifdef _DEBUG
#include "core/system/debug.hpp" // Para Debug
#endif
namespace GlobalInputs { namespace GlobalInputs {
// Funciones internas // Funciones internas
@@ -27,7 +25,7 @@ namespace GlobalInputs {
if (stringInVector(Notifier::get()->getCodes(), CODE)) { if (stringInVector(Notifier::get()->getCodes(), CODE)) {
SceneManager::current = SceneManager::Scene::TITLE; SceneManager::current = SceneManager::Scene::TITLE;
} else { } else {
Notifier::get()->show({CODE}, Notifier::Style::DEFAULT, -1, true, CODE); Notifier::get()->show({Locale::get()->get("ui.press_again_menu")}, Notifier::Style::DEFAULT, -1, true, CODE); // NOLINT(readability-static-accessed-through-instance)
} }
return; return;
} }
@@ -47,7 +45,7 @@ namespace GlobalInputs {
if (stringInVector(Notifier::get()->getCodes(), CODE)) { if (stringInVector(Notifier::get()->getCodes(), CODE)) {
SceneManager::current = SceneManager::Scene::QUIT; SceneManager::current = SceneManager::Scene::QUIT;
} else { } else {
Notifier::get()->show({CODE}, Notifier::Style::DEFAULT, -1, true, CODE); Notifier::get()->show({Locale::get()->get("ui.press_again_exit")}, Notifier::Style::DEFAULT, -1, true, CODE); // NOLINT(readability-static-accessed-through-instance)
} }
} }
@@ -71,73 +69,65 @@ namespace GlobalInputs {
void handleToggleBorder() { void handleToggleBorder() {
Screen::get()->toggleBorder(); Screen::get()->toggleBorder();
Notifier::get()->show({"BORDER " + std::string(Options::video.border.enabled ? "ENABLED" : "DISABLED")}); Notifier::get()->show({Locale::get()->get(Options::video.border.enabled ? "ui.border_enabled" : "ui.border_disabled")}); // NOLINT(readability-static-accessed-through-instance)
} }
void handleToggleVideoMode() { void handleToggleVideoMode() {
Screen::get()->toggleVideoMode(); Screen::get()->toggleVideoMode();
Notifier::get()->show({"FULLSCREEN " + std::string(static_cast<int>(Options::video.fullscreen) == 0 ? "DISABLED" : "ENABLED")}); Notifier::get()->show({Locale::get()->get(static_cast<int>(Options::video.fullscreen) == 0 ? "ui.fullscreen_disabled" : "ui.fullscreen_enabled")}); // NOLINT(readability-static-accessed-through-instance)
} }
void handleDecWindowZoom() { void handleDecWindowZoom() {
if (Screen::get()->decWindowZoom()) { if (Screen::get()->decWindowZoom()) {
Notifier::get()->show({"WINDOW ZOOM x" + std::to_string(Options::window.zoom)}); Notifier::get()->show({Locale::get()->get("ui.window_zoom") + std::to_string(Options::window.zoom)}); // NOLINT(readability-static-accessed-through-instance)
} }
} }
void handleIncWindowZoom() { void handleIncWindowZoom() {
if (Screen::get()->incWindowZoom()) { if (Screen::get()->incWindowZoom()) {
Notifier::get()->show({"WINDOW ZOOM x" + std::to_string(Options::window.zoom)}); Notifier::get()->show({Locale::get()->get("ui.window_zoom") + std::to_string(Options::window.zoom)}); // NOLINT(readability-static-accessed-through-instance)
} }
} }
void handleTogglePostFX() { void handleTogglePostFX() {
Screen::get()->togglePostFX(); Screen::get()->togglePostFX();
Notifier::get()->show({"POSTFX " + std::string(Options::video.postfx ? "ENABLED" : "DISABLED")}); Notifier::get()->show({Locale::get()->get(Options::video.postfx ? "ui.postfx_enabled" : "ui.postfx_disabled")}); // NOLINT(readability-static-accessed-through-instance)
}
void handleToggleSupersampling() {
Screen::get()->toggleSupersampling();
Notifier::get()->show({Locale::get()->get(Options::video.supersampling ? "ui.supersampling_enabled" : "ui.supersampling_disabled")}); // NOLINT(readability-static-accessed-through-instance)
} }
void handleNextPostFXPreset() { void handleNextPostFXPreset() {
if (!Options::postfx_presets.empty()) { if (!Options::postfx_presets.empty()) {
Options::current_postfx_preset = (Options::current_postfx_preset + 1) % static_cast<int>(Options::postfx_presets.size()); Options::current_postfx_preset = (Options::current_postfx_preset + 1) % static_cast<int>(Options::postfx_presets.size());
Screen::get()->reloadPostFX(); Screen::get()->reloadPostFX();
Notifier::get()->show({"POSTFX " + Options::postfx_presets[static_cast<size_t>(Options::current_postfx_preset)].name}); Notifier::get()->show({Locale::get()->get("ui.postfx") + " " + Options::postfx_presets[static_cast<size_t>(Options::current_postfx_preset)].name}); // NOLINT(readability-static-accessed-through-instance)
} }
} }
void handleNextPalette() { void handleNextPalette() {
Screen::get()->nextPalette(); Screen::get()->nextPalette();
Notifier::get()->show({"PALETTE " + Options::video.palette}); Notifier::get()->show({Locale::get()->get("ui.palette") + " " + Options::video.palette}); // NOLINT(readability-static-accessed-through-instance)
} }
void handlePreviousPalette() { void handlePreviousPalette() {
Screen::get()->previousPalette(); Screen::get()->previousPalette();
Notifier::get()->show({"PALETTE " + Options::video.palette}); Notifier::get()->show({Locale::get()->get("ui.palette") + " " + Options::video.palette}); // NOLINT(readability-static-accessed-through-instance)
} }
void handleToggleIntegerScale() { void handleToggleIntegerScale() {
Screen::get()->toggleIntegerScale(); Screen::get()->toggleIntegerScale();
Screen::get()->setVideoMode(Options::video.fullscreen); Screen::get()->setVideoMode(Options::video.fullscreen);
Notifier::get()->show({"INTEGER SCALE " + std::string(Options::video.integer_scale ? "ENABLED" : "DISABLED")}); Notifier::get()->show({Locale::get()->get(Options::video.integer_scale ? "ui.integer_scale_enabled" : "ui.integer_scale_disabled")}); // NOLINT(readability-static-accessed-through-instance)
} }
void handleToggleVSync() { void handleToggleVSync() {
Screen::get()->toggleVSync(); Screen::get()->toggleVSync();
Notifier::get()->show({"V-SYNC " + std::string(Options::video.vertical_sync ? "ENABLED" : "DISABLED")}); Notifier::get()->show({Locale::get()->get(Options::video.vertical_sync ? "ui.vsync_enabled" : "ui.vsync_disabled")}); // NOLINT(readability-static-accessed-through-instance)
} }
#ifdef _DEBUG
void handleShowDebugInfo() {
Screen::get()->toggleDebugInfo();
}
/*
void handleToggleDebug() {
Debug::get()->toggleEnabled();
Notifier::get()->show({"DEBUG " + std::string(Debug::get()->isEnabled() ? "ENABLED" : "DISABLED")}, Notifier::TextAlign::CENTER);
}
*/
#endif
// Detecta qué acción global ha sido presionada (si alguna) // Detecta qué acción global ha sido presionada (si alguna)
auto getPressedAction() -> InputAction { auto getPressedAction() -> InputAction {
if (Input::get()->checkAction(InputAction::EXIT, Input::DO_NOT_ALLOW_REPEAT)) { if (Input::get()->checkAction(InputAction::EXIT, Input::DO_NOT_ALLOW_REPEAT)) {
@@ -161,10 +151,13 @@ void handleToggleDebug() {
} }
} }
if (Input::get()->checkAction(InputAction::TOGGLE_POSTFX, Input::DO_NOT_ALLOW_REPEAT)) { if (Input::get()->checkAction(InputAction::TOGGLE_POSTFX, Input::DO_NOT_ALLOW_REPEAT)) {
if (Options::video.postfx && ((SDL_GetModState() & SDL_KMOD_SHIFT) != 0U)) { if ((SDL_GetModState() & SDL_KMOD_CTRL) != 0U) {
return InputAction::NEXT_POSTFX_PRESET; return InputAction::TOGGLE_SUPERSAMPLING; // Ctrl+F4
} }
return InputAction::TOGGLE_POSTFX; if (Options::video.postfx && ((SDL_GetModState() & SDL_KMOD_SHIFT) != 0U)) {
return InputAction::NEXT_POSTFX_PRESET; // Shift+F4
}
return InputAction::TOGGLE_POSTFX; // F4
} }
if (Input::get()->checkAction(InputAction::NEXT_PALETTE, Input::DO_NOT_ALLOW_REPEAT)) { if (Input::get()->checkAction(InputAction::NEXT_PALETTE, Input::DO_NOT_ALLOW_REPEAT)) {
return InputAction::NEXT_PALETTE; return InputAction::NEXT_PALETTE;
@@ -184,6 +177,9 @@ void handleToggleDebug() {
if (Input::get()->checkAction(InputAction::SHOW_DEBUG_INFO, Input::DO_NOT_ALLOW_REPEAT)) { if (Input::get()->checkAction(InputAction::SHOW_DEBUG_INFO, Input::DO_NOT_ALLOW_REPEAT)) {
return InputAction::SHOW_DEBUG_INFO; return InputAction::SHOW_DEBUG_INFO;
} }
if (Input::get()->checkAction(InputAction::TOGGLE_CONSOLE, Input::DO_NOT_ALLOW_REPEAT)) {
return InputAction::TOGGLE_CONSOLE;
}
return InputAction::NONE; return InputAction::NONE;
} }
@@ -193,6 +189,15 @@ void handleToggleDebug() {
// Comprueba los inputs que se pueden introducir en cualquier sección del juego // Comprueba los inputs que se pueden introducir en cualquier sección del juego
void handle() { void handle() {
// Si la consola está activa, bloquea el resto de inputs globales
if (Console::get() != nullptr && Console::get()->isActive()) {
if (Input::get()->checkAction(InputAction::TOGGLE_CONSOLE, Input::DO_NOT_ALLOW_REPEAT) ||
Input::get()->checkAction(InputAction::EXIT, Input::DO_NOT_ALLOW_REPEAT)) {
Console::get()->toggle();
}
return;
}
// Salida de administrador en modo kiosko (Ctrl+Shift+Alt+Q) // Salida de administrador en modo kiosko (Ctrl+Shift+Alt+Q)
if (Options::kiosk.enabled) { if (Options::kiosk.enabled) {
SDL_Keymod mod = SDL_GetModState(); SDL_Keymod mod = SDL_GetModState();
@@ -240,6 +245,10 @@ void handleToggleDebug() {
handleNextPostFXPreset(); handleNextPostFXPreset();
break; break;
case InputAction::TOGGLE_SUPERSAMPLING:
handleToggleSupersampling();
break;
case InputAction::NEXT_PALETTE: case InputAction::NEXT_PALETTE:
handleNextPalette(); handleNextPalette();
break; break;
@@ -256,13 +265,16 @@ void handleToggleDebug() {
handleToggleVSync(); handleToggleVSync();
break; break;
case InputAction::TOGGLE_DEBUG: case InputAction::TOGGLE_CONSOLE:
// handleToggleDebug(); if (Console::get() != nullptr) { Console::get()->toggle(); }
break; break;
#ifdef _DEBUG #ifdef _DEBUG
case InputAction::TOGGLE_DEBUG:
Screen::get()->toggleFPS();
break;
case InputAction::SHOW_DEBUG_INFO: case InputAction::SHOW_DEBUG_INFO:
handleShowDebugInfo();
break; break;
#endif #endif

View File

@@ -14,7 +14,7 @@
Input* Input::instance = nullptr; Input* Input::instance = nullptr;
// Inicializa la instancia única del singleton // Inicializa la instancia única del singleton
void Input::init(const std::string& game_controller_db_path) { void Input::init(const std::string& game_controller_db_path) { // NOLINT(readability-convert-member-functions-to-static)
Input::instance = new Input(game_controller_db_path); Input::instance = new Input(game_controller_db_path);
} }
@@ -51,7 +51,8 @@ Input::Input(std::string game_controller_db_path)
{Action::TOGGLE_BORDER, KeyState{.scancode = SDL_SCANCODE_F9}}, {Action::TOGGLE_BORDER, KeyState{.scancode = SDL_SCANCODE_F9}},
{Action::TOGGLE_VSYNC, KeyState{.scancode = SDL_SCANCODE_F10}}, {Action::TOGGLE_VSYNC, KeyState{.scancode = SDL_SCANCODE_F10}},
{Action::PAUSE, KeyState{.scancode = SDL_SCANCODE_F11}}, {Action::PAUSE, KeyState{.scancode = SDL_SCANCODE_F11}},
{Action::TOGGLE_DEBUG, KeyState{.scancode = SDL_SCANCODE_F12}}}; {Action::TOGGLE_DEBUG, KeyState{.scancode = SDL_SCANCODE_F12}},
{Action::TOGGLE_CONSOLE, KeyState{.scancode = SDL_SCANCODE_TAB}}};
initSDLGamePad(); // Inicializa el subsistema SDL_INIT_GAMEPAD initSDLGamePad(); // Inicializa el subsistema SDL_INIT_GAMEPAD
} }
@@ -69,7 +70,7 @@ void Input::applyKeyboardBindingsFromOptions() {
} }
// Aplica configuración de botones del gamepad desde Options al primer gamepad conectado // Aplica configuración de botones del gamepad desde Options al primer gamepad conectado
void Input::applyGamepadBindingsFromOptions() { void Input::applyGamepadBindingsFromOptions() { // NOLINT(readability-convert-member-functions-to-static)
// Si no hay gamepads conectados, no hay nada que hacer // Si no hay gamepads conectados, no hay nada que hacer
if (gamepads_.empty()) { if (gamepads_.empty()) {
return; return;
@@ -90,21 +91,21 @@ void Input::applyGamepadBindingsFromOptions() {
} }
// Asigna inputs a botones del mando // Asigna inputs a botones del mando
void Input::bindGameControllerButton(const std::shared_ptr<Gamepad>& gamepad, Action action, SDL_GamepadButton button) { void Input::bindGameControllerButton(const std::shared_ptr<Gamepad>& gamepad, Action action, SDL_GamepadButton button) { // NOLINT(readability-convert-member-functions-to-static)
if (gamepad != nullptr) { if (gamepad != nullptr) {
gamepad->bindings[action].button = button; gamepad->bindings[action].button = button;
} }
} }
// Asigna inputs a botones del mando // Asigna inputs a botones del mando
void Input::bindGameControllerButton(const std::shared_ptr<Gamepad>& gamepad, Action action_target, Action action_source) { void Input::bindGameControllerButton(const std::shared_ptr<Gamepad>& gamepad, Action action_target, Action action_source) { // NOLINT(readability-convert-member-functions-to-static)
if (gamepad != nullptr) { if (gamepad != nullptr) {
gamepad->bindings[action_target].button = gamepad->bindings[action_source].button; gamepad->bindings[action_target].button = gamepad->bindings[action_source].button;
} }
} }
// Comprueba si alguna acción está activa // Comprueba si alguna acción está activa
auto Input::checkAction(Action action, bool repeat, bool check_keyboard, const std::shared_ptr<Gamepad>& gamepad) -> bool { auto Input::checkAction(Action action, bool repeat, bool check_keyboard, const std::shared_ptr<Gamepad>& gamepad) -> bool { // NOLINT(readability-convert-member-functions-to-static)
bool success_keyboard = false; bool success_keyboard = false;
bool success_controller = false; bool success_controller = false;
@@ -142,7 +143,7 @@ auto Input::checkAction(Action action, bool repeat, bool check_keyboard, const s
} }
// Comprueba si hay almenos una acción activa // Comprueba si hay almenos una acción activa
auto Input::checkAnyInput(bool check_keyboard, const std::shared_ptr<Gamepad>& gamepad) -> bool { auto Input::checkAnyInput(bool check_keyboard, const std::shared_ptr<Gamepad>& gamepad) -> bool { // NOLINT(readability-convert-member-functions-to-static)
// Obtenemos el número total de acciones posibles para iterar sobre ellas. // Obtenemos el número total de acciones posibles para iterar sobre ellas.
// --- Comprobación del Teclado --- // --- Comprobación del Teclado ---
@@ -179,7 +180,7 @@ auto Input::checkAnyInput(bool check_keyboard, const std::shared_ptr<Gamepad>& g
} }
// Comprueba si hay algún botón pulsado // Comprueba si hay algún botón pulsado
auto Input::checkAnyButton(bool repeat) -> bool { auto Input::checkAnyButton(bool repeat) -> bool { // NOLINT(readability-convert-member-functions-to-static)
// Solo comprueba los botones definidos previamente // Solo comprueba los botones definidos previamente
for (auto bi : BUTTON_INPUTS) { for (auto bi : BUTTON_INPUTS) {
// Comprueba el teclado // Comprueba el teclado
@@ -219,7 +220,7 @@ auto Input::getControllerNames() const -> std::vector<std::string> {
auto Input::getNumGamepads() const -> int { return gamepads_.size(); } auto Input::getNumGamepads() const -> int { return gamepads_.size(); }
// Obtiene el gamepad a partir de un event.id // Obtiene el gamepad a partir de un event.id
auto Input::getGamepad(SDL_JoystickID id) const -> std::shared_ptr<Input::Gamepad> { auto Input::getGamepad(SDL_JoystickID id) const -> std::shared_ptr<Input::Gamepad> { // NOLINT(readability-convert-member-functions-to-static)
for (const auto& gamepad : gamepads_) { for (const auto& gamepad : gamepads_) {
if (gamepad->instance_id == id) { if (gamepad->instance_id == id) {
return gamepad; return gamepad;
@@ -228,7 +229,7 @@ auto Input::getGamepad(SDL_JoystickID id) const -> std::shared_ptr<Input::Gamepa
return nullptr; return nullptr;
} }
auto Input::getGamepadByName(const std::string& name) const -> std::shared_ptr<Input::Gamepad> { auto Input::getGamepadByName(const std::string& name) const -> std::shared_ptr<Input::Gamepad> { // NOLINT(readability-convert-member-functions-to-static)
for (const auto& gamepad : gamepads_) { for (const auto& gamepad : gamepads_) {
if (gamepad && gamepad->name == name) { if (gamepad && gamepad->name == name) {
return gamepad; return gamepad;
@@ -238,12 +239,12 @@ auto Input::getGamepadByName(const std::string& name) const -> std::shared_ptr<I
} }
// Obtiene el SDL_GamepadButton asignado a un action // Obtiene el SDL_GamepadButton asignado a un action
auto Input::getControllerBinding(const std::shared_ptr<Gamepad>& gamepad, Action action) -> SDL_GamepadButton { auto Input::getControllerBinding(const std::shared_ptr<Gamepad>& gamepad, Action action) -> SDL_GamepadButton { // NOLINT(readability-convert-member-functions-to-static)
return static_cast<SDL_GamepadButton>(gamepad->bindings[action].button); return static_cast<SDL_GamepadButton>(gamepad->bindings[action].button);
} }
// Comprueba el eje del mando // Comprueba el eje del mando
auto Input::checkAxisInput(Action action, const std::shared_ptr<Gamepad>& gamepad, bool repeat) -> bool { auto Input::checkAxisInput(Action action, const std::shared_ptr<Gamepad>& gamepad, bool repeat) -> bool { // NOLINT(readability-convert-member-functions-to-static)
// Obtener el binding configurado para esta acción // Obtener el binding configurado para esta acción
auto& binding = gamepad->bindings[action]; auto& binding = gamepad->bindings[action];
@@ -286,7 +287,7 @@ auto Input::checkAxisInput(Action action, const std::shared_ptr<Gamepad>& gamepa
} }
// Comprueba los triggers del mando como botones digitales // Comprueba los triggers del mando como botones digitales
auto Input::checkTriggerInput(Action action, const std::shared_ptr<Gamepad>& gamepad, bool repeat) -> bool { auto Input::checkTriggerInput(Action action, const std::shared_ptr<Gamepad>& gamepad, bool repeat) -> bool { // NOLINT(readability-convert-member-functions-to-static)
// Solo manejamos botones específicos que pueden ser triggers // Solo manejamos botones específicos que pueden ser triggers
if (gamepad->bindings[action].button != static_cast<int>(SDL_GAMEPAD_BUTTON_INVALID)) { if (gamepad->bindings[action].button != static_cast<int>(SDL_GAMEPAD_BUTTON_INVALID)) {
// Solo procesamos L2 y R2 como triggers // Solo procesamos L2 y R2 como triggers
@@ -333,13 +334,13 @@ auto Input::checkTriggerInput(Action action, const std::shared_ptr<Gamepad>& gam
return false; return false;
} }
void Input::addGamepadMappingsFromFile() { void Input::addGamepadMappingsFromFile() { // NOLINT(readability-convert-member-functions-to-static)
if (SDL_AddGamepadMappingsFromFile(gamepad_mappings_file_.c_str()) < 0) { if (SDL_AddGamepadMappingsFromFile(gamepad_mappings_file_.c_str()) < 0) {
std::cout << "Error, could not load " << gamepad_mappings_file_.c_str() << " file: " << SDL_GetError() << '\n'; std::cout << "Error, could not load " << gamepad_mappings_file_.c_str() << " file: " << SDL_GetError() << '\n';
} }
} }
void Input::discoverGamepads() { void Input::discoverGamepads() { // NOLINT(readability-convert-member-functions-to-static)
SDL_Event event; SDL_Event event;
while (SDL_PollEvent(&event)) { while (SDL_PollEvent(&event)) {
handleEvent(event); // Comprueba mandos conectados handleEvent(event); // Comprueba mandos conectados
@@ -375,7 +376,7 @@ void Input::resetInputStates() {
} }
} }
void Input::update() { void Input::update() { // NOLINT(readability-convert-member-functions-to-static)
// --- TECLADO --- // --- TECLADO ---
const bool* key_states = SDL_GetKeyboardState(nullptr); const bool* key_states = SDL_GetKeyboardState(nullptr);
@@ -399,7 +400,7 @@ void Input::update() {
} }
} }
auto Input::handleEvent(const SDL_Event& event) -> std::string { auto Input::handleEvent(const SDL_Event& event) -> std::string { // NOLINT(readability-convert-member-functions-to-static)
switch (event.type) { switch (event.type) {
case SDL_EVENT_GAMEPAD_ADDED: case SDL_EVENT_GAMEPAD_ADDED:
return addGamepad(event.gdevice.which); return addGamepad(event.gdevice.which);
@@ -409,7 +410,7 @@ auto Input::handleEvent(const SDL_Event& event) -> std::string {
return {}; return {};
} }
auto Input::addGamepad(int device_index) -> std::string { auto Input::addGamepad(int device_index) -> std::string { // NOLINT(readability-convert-member-functions-to-static)
SDL_Gamepad* pad = SDL_OpenGamepad(device_index); SDL_Gamepad* pad = SDL_OpenGamepad(device_index);
if (pad == nullptr) { if (pad == nullptr) {
std::cerr << "Error al abrir el gamepad: " << SDL_GetError() << '\n'; std::cerr << "Error al abrir el gamepad: " << SDL_GetError() << '\n';
@@ -423,8 +424,8 @@ auto Input::addGamepad(int device_index) -> std::string {
return name + " CONNECTED"; return name + " CONNECTED";
} }
auto Input::removeGamepad(SDL_JoystickID id) -> std::string { auto Input::removeGamepad(SDL_JoystickID id) -> std::string { // NOLINT(readability-convert-member-functions-to-static)
auto it = std::ranges::find_if(gamepads_, [id](const std::shared_ptr<Gamepad>& gamepad) { auto it = std::ranges::find_if(gamepads_, [id](const std::shared_ptr<Gamepad>& gamepad) -> bool {
return gamepad->instance_id == id; return gamepad->instance_id == id;
}); });
@@ -438,7 +439,7 @@ auto Input::removeGamepad(SDL_JoystickID id) -> std::string {
return {}; return {};
} }
void Input::printConnectedGamepads() const { void Input::printConnectedGamepads() const { // NOLINT(readability-convert-member-functions-to-static)
if (gamepads_.empty()) { if (gamepads_.empty()) {
std::cout << "No hay gamepads conectados." << '\n'; std::cout << "No hay gamepads conectados." << '\n';
return; return;
@@ -452,7 +453,7 @@ void Input::printConnectedGamepads() const {
} }
} }
auto Input::findAvailableGamepadByName(const std::string& gamepad_name) -> std::shared_ptr<Input::Gamepad> { auto Input::findAvailableGamepadByName(const std::string& gamepad_name) -> std::shared_ptr<Input::Gamepad> { // NOLINT(readability-convert-member-functions-to-static)
// Si no hay gamepads disponibles, devolver gamepad por defecto // Si no hay gamepads disponibles, devolver gamepad por defecto
if (gamepads_.empty()) { if (gamepads_.empty()) {
return nullptr; return nullptr;

View File

@@ -101,12 +101,12 @@ class Input {
// --- Gestión de gamepads --- // --- Gestión de gamepads ---
[[nodiscard]] auto gameControllerFound() const -> bool; [[nodiscard]] auto gameControllerFound() const -> bool;
[[nodiscard]] auto getNumGamepads() const -> int; [[nodiscard]] auto getNumGamepads() const -> int;
auto getGamepad(SDL_JoystickID id) const -> std::shared_ptr<Gamepad>; [[nodiscard]] auto getGamepad(SDL_JoystickID id) const -> std::shared_ptr<Gamepad>;
auto getGamepadByName(const std::string& name) const -> std::shared_ptr<Input::Gamepad>; [[nodiscard]] auto getGamepadByName(const std::string& name) const -> std::shared_ptr<Input::Gamepad>;
auto getGamepads() const -> const Gamepads& { return gamepads_; } [[nodiscard]] auto getGamepads() const -> const Gamepads& { return gamepads_; }
auto findAvailableGamepadByName(const std::string& gamepad_name) -> std::shared_ptr<Gamepad>; auto findAvailableGamepadByName(const std::string& gamepad_name) -> std::shared_ptr<Gamepad>;
static auto getControllerName(const std::shared_ptr<Gamepad>& gamepad) -> std::string; static auto getControllerName(const std::shared_ptr<Gamepad>& gamepad) -> std::string;
auto getControllerNames() const -> std::vector<std::string>; [[nodiscard]] auto getControllerNames() const -> std::vector<std::string>;
[[nodiscard]] static auto getControllerBinding(const std::shared_ptr<Gamepad>& gamepad, Action action) -> SDL_GamepadButton; [[nodiscard]] static auto getControllerBinding(const std::shared_ptr<Gamepad>& gamepad, Action action) -> SDL_GamepadButton;
void printConnectedGamepads() const; void printConnectedGamepads() const;

View File

@@ -26,12 +26,14 @@ enum class InputAction : int { // Acciones de entrada posibles en el juego
TOGGLE_INTEGER_SCALE, TOGGLE_INTEGER_SCALE,
TOGGLE_POSTFX, TOGGLE_POSTFX,
NEXT_POSTFX_PRESET, NEXT_POSTFX_PRESET,
TOGGLE_SUPERSAMPLING,
TOGGLE_BORDER, TOGGLE_BORDER,
TOGGLE_MUSIC, TOGGLE_MUSIC,
NEXT_PALETTE, NEXT_PALETTE,
PREVIOUS_PALETTE, PREVIOUS_PALETTE,
SHOW_DEBUG_INFO, SHOW_DEBUG_INFO,
TOGGLE_DEBUG, TOGGLE_DEBUG,
TOGGLE_CONSOLE,
// Input obligatorio // Input obligatorio
NONE, NONE,

View File

@@ -0,0 +1,90 @@
#include "core/locale/locale.hpp"
#include <fstream>
#include <iostream>
#include <string>
#include "external/fkyaml_node.hpp" // Para fkyaml::node
#include "game/options.hpp" // Para Options::console
// [SINGLETON]
Locale* Locale::instance = nullptr;
// [SINGLETON] Crea el objeto con esta función estática
void Locale::init(const std::string& file_path) { // NOLINT(readability-convert-member-functions-to-static)
Locale::instance = new Locale();
Locale::instance->loadFromFile(file_path);
}
// [SINGLETON] Destruye el objeto con esta función estática
void Locale::destroy() {
delete Locale::instance;
Locale::instance = nullptr;
}
// [SINGLETON] Con este método obtenemos el objeto y podemos trabajar con él
auto Locale::get() -> Locale* {
return Locale::instance;
}
// Devuelve la traducción de la clave o la clave como fallback
auto Locale::get(const std::string& key) const -> std::string { // NOLINT(readability-convert-member-functions-to-static)
auto it = strings_.find(key);
if (it != strings_.end()) {
return it->second;
}
if (Options::console) {
std::cerr << "Locale: clave no encontrada: " << key << '\n';
}
return key;
}
// Aplana un nodo YAML de forma recursiva: {a: {b: "val"}} -> {"a.b" -> "val"}
void Locale::flatten(const void* node_ptr, const std::string& prefix) { // NOLINT(readability-convert-member-functions-to-static)
const auto& node = *static_cast<const fkyaml::node*>(node_ptr);
for (auto itr = node.begin(); itr != node.end(); ++itr) {
const std::string KEY = prefix.empty()
? itr.key().get_value<std::string>()
: prefix + "." + itr.key().get_value<std::string>();
const auto& value = itr.value();
if (value.is_mapping()) {
flatten(&value, KEY);
} else if (value.is_string()) {
strings_[KEY] = value.get_value<std::string>();
}
}
}
// Carga las traducciones desde el fichero YAML indicado
void Locale::loadFromFile(const std::string& file_path) { // NOLINT(readability-convert-member-functions-to-static)
if (file_path.empty()) {
if (Options::console) {
std::cerr << "Locale: ruta de fichero vacía, sin traducciones cargadas\n";
}
return;
}
std::ifstream file(file_path);
if (!file.is_open()) {
if (Options::console) {
std::cerr << "Locale: no se puede abrir " << file_path << '\n';
}
return;
}
try {
auto yaml = fkyaml::node::deserialize(file);
flatten(&yaml, "");
if (Options::console) {
std::cout << "Locale: " << strings_.size() << " traducciones cargadas desde " << file_path << '\n';
}
} catch (const fkyaml::exception& e) {
if (Options::console) {
std::cerr << "Locale: error al parsear YAML: " << e.what() << '\n';
}
}
}

View File

@@ -0,0 +1,26 @@
#pragma once
#include <string>
#include <unordered_map>
// Clase Locale: gestiona las traducciones del juego (singleton)
// Las traducciones se cargan desde un fichero YAML en el inicio.
// No se permite cambio de idioma en caliente.
class Locale {
public:
static void init(const std::string& file_path); // Crea e inicializa el singleton
static void destroy(); // Destruye el singleton
static auto get() -> Locale*; // Devuelve el singleton
// Devuelve la traducción de la clave dada.
// Si la clave no existe, devuelve la propia clave como fallback.
[[nodiscard]] auto get(const std::string& key) const -> std::string;
private:
Locale() = default;
void loadFromFile(const std::string& file_path);
void flatten(const void* node_ptr, const std::string& prefix); // Aplana nodos YAML anidados
static Locale* instance;
std::unordered_map<std::string, std::string> strings_;
};

View File

@@ -15,7 +15,7 @@ namespace GIF {
} }
// Inicializa el diccionario LZW con los valores iniciales // Inicializa el diccionario LZW con los valores iniciales
inline void initializeDictionary(std::vector<DictionaryEntry>& dictionary, int code_length, int& dictionary_ind) { inline void initializeDictionary(std::vector<DictionaryEntry>& dictionary, int code_length, int& dictionary_ind) { // NOLINT(readability-identifier-naming)
int size = 1 << code_length; int size = 1 << code_length;
dictionary.resize(1 << (code_length + 1)); dictionary.resize(1 << (code_length + 1));
for (dictionary_ind = 0; dictionary_ind < size; dictionary_ind++) { for (dictionary_ind = 0; dictionary_ind < size; dictionary_ind++) {
@@ -55,7 +55,7 @@ namespace GIF {
} }
// Agrega una nueva entrada al diccionario // Agrega una nueva entrada al diccionario
inline void addDictionaryEntry(std::vector<DictionaryEntry>& dictionary, int& dictionary_ind, int& code_length, int prev, int code) { inline void addDictionaryEntry(std::vector<DictionaryEntry>& dictionary, int& dictionary_ind, int& code_length, int prev, int code) { // NOLINT(readability-identifier-naming)
uint8_t first_byte; uint8_t first_byte;
if (code == dictionary_ind) { if (code == dictionary_ind) {
first_byte = findFirstByte(dictionary, prev); first_byte = findFirstByte(dictionary, prev);
@@ -90,7 +90,7 @@ namespace GIF {
return match_len; return match_len;
} }
void Gif::decompress(int code_length, const uint8_t* input, int input_length, uint8_t* out) { void Gif::decompress(int code_length, const uint8_t* input, int input_length, uint8_t* out) { // NOLINT(readability-convert-member-functions-to-static)
// Verifica que el code_length tenga un rango razonable. // Verifica que el code_length tenga un rango razonable.
if (code_length < 2 || code_length > 12) { if (code_length < 2 || code_length > 12) {
throw std::runtime_error("Invalid LZW code length"); throw std::runtime_error("Invalid LZW code length");
@@ -146,7 +146,7 @@ namespace GIF {
} }
} }
auto Gif::readSubBlocks(const uint8_t*& buffer) -> std::vector<uint8_t> { auto Gif::readSubBlocks(const uint8_t*& buffer) -> std::vector<uint8_t> { // NOLINT(readability-convert-member-functions-to-static)
std::vector<uint8_t> data; std::vector<uint8_t> data;
uint8_t block_size = *buffer; uint8_t block_size = *buffer;
buffer++; buffer++;
@@ -159,7 +159,7 @@ namespace GIF {
return data; return data;
} }
auto Gif::processImageDescriptor(const uint8_t*& buffer, const std::vector<RGB>& gct, int resolution_bits) -> std::vector<uint8_t> { auto Gif::processImageDescriptor(const uint8_t*& buffer, const std::vector<RGB>& gct, int resolution_bits) -> std::vector<uint8_t> { // NOLINT(readability-convert-member-functions-to-static)
ImageDescriptor image_descriptor; ImageDescriptor image_descriptor;
// Lee 9 bytes para el image descriptor. // Lee 9 bytes para el image descriptor.
readBytes(buffer, &image_descriptor, sizeof(ImageDescriptor)); readBytes(buffer, &image_descriptor, sizeof(ImageDescriptor));
@@ -175,7 +175,7 @@ namespace GIF {
return uncompressed_data; return uncompressed_data;
} }
auto Gif::loadPalette(const uint8_t* buffer) -> std::vector<uint32_t> { auto Gif::loadPalette(const uint8_t* buffer) -> std::vector<uint32_t> { // NOLINT(readability-convert-member-functions-to-static)
uint8_t header[6]; uint8_t header[6];
std::memcpy(header, buffer, 6); std::memcpy(header, buffer, 6);
buffer += 6; buffer += 6;
@@ -186,7 +186,7 @@ namespace GIF {
std::vector<uint32_t> global_color_table; std::vector<uint32_t> global_color_table;
if ((screen_descriptor.fields & 0x80) != 0) { if ((screen_descriptor.fields & 0x80) != 0) {
int global_color_table_size = 1 << (((screen_descriptor.fields & 0x07) + 1)); int global_color_table_size = 1 << ((screen_descriptor.fields & 0x07) + 1);
global_color_table.resize(global_color_table_size); global_color_table.resize(global_color_table_size);
for (int i = 0; i < global_color_table_size; ++i) { for (int i = 0; i < global_color_table_size; ++i) {
uint8_t r = buffer[0]; uint8_t r = buffer[0];
@@ -199,7 +199,7 @@ namespace GIF {
return global_color_table; return global_color_table;
} }
auto Gif::processGifStream(const uint8_t* buffer, uint16_t& w, uint16_t& h) -> std::vector<uint8_t> { auto Gif::processGifStream(const uint8_t* buffer, uint16_t& w, uint16_t& h) -> std::vector<uint8_t> { // NOLINT(readability-convert-member-functions-to-static)
// Leer la cabecera de 6 bytes ("GIF87a" o "GIF89a") // Leer la cabecera de 6 bytes ("GIF87a" o "GIF89a")
uint8_t header[6]; uint8_t header[6];
std::memcpy(header, buffer, 6); std::memcpy(header, buffer, 6);
@@ -222,7 +222,7 @@ namespace GIF {
int color_resolution_bits = ((screen_descriptor.fields & 0x70) >> 4) + 1; int color_resolution_bits = ((screen_descriptor.fields & 0x70) >> 4) + 1;
std::vector<RGB> global_color_table; std::vector<RGB> global_color_table;
if ((screen_descriptor.fields & 0x80) != 0) { if ((screen_descriptor.fields & 0x80) != 0) {
int global_color_table_size = 1 << (((screen_descriptor.fields & 0x07) + 1)); int global_color_table_size = 1 << ((screen_descriptor.fields & 0x07) + 1);
global_color_table.resize(global_color_table_size); global_color_table.resize(global_color_table_size);
std::memcpy(global_color_table.data(), buffer, 3 * global_color_table_size); std::memcpy(global_color_table.data(), buffer, 3 * global_color_table_size);
buffer += 3 * global_color_table_size; buffer += 3 * global_color_table_size;

View File

@@ -65,11 +65,8 @@ PixelReveal::PixelReveal(int width, int height, float pixels_per_second, float s
} }
} }
// Destructor
PixelReveal::~PixelReveal() = default;
// Actualiza el estado del revelado // Actualiza el estado del revelado
void PixelReveal::update(float time_active) { void PixelReveal::update(float time_active) { // NOLINT(readability-make-member-function-const)
// En modo normal revela (pone transparente); en modo inverso cubre (pone negro) // En modo normal revela (pone transparente); en modo inverso cubre (pone negro)
const auto PIXEL_COLOR = reverse_ ? static_cast<Uint8>(PaletteColor::BLACK) : static_cast<Uint8>(PaletteColor::TRANSPARENT); const auto PIXEL_COLOR = reverse_ ? static_cast<Uint8>(PaletteColor::BLACK) : static_cast<Uint8>(PaletteColor::TRANSPARENT);
@@ -106,5 +103,5 @@ void PixelReveal::render(int dst_x, int dst_y) const {
// Indica si el revelado ha completado todas las filas // Indica si el revelado ha completado todas las filas
auto PixelReveal::isComplete() const -> bool { auto PixelReveal::isComplete() const -> bool {
return std::ranges::all_of(row_step_, [this](int s) { return s >= num_steps_; }); return std::ranges::all_of(row_step_, [this](int s) -> bool { return s >= num_steps_; });
} }

View File

@@ -16,8 +16,7 @@ class PixelReveal {
// Constructor // Constructor
PixelReveal(int width, int height, float pixels_per_second, float step_duration, int num_steps = 4, bool reverse = false, RevealMode mode = RevealMode::RANDOM); PixelReveal(int width, int height, float pixels_per_second, float step_duration, int num_steps = 4, bool reverse = false, RevealMode mode = RevealMode::RANDOM);
// Destructor definido en el .cpp para que unique_ptr<Surface> funcione con forward declaration ~PixelReveal() = default;
~PixelReveal();
// Actualiza el estado del revelado según el tiempo transcurrido // Actualiza el estado del revelado según el tiempo transcurrido
void update(float time_active); void update(float time_active);

View File

@@ -4,6 +4,7 @@
#include <algorithm> // Para max, min, transform #include <algorithm> // Para max, min, transform
#include <cctype> // Para toupper #include <cctype> // Para toupper
#include <cstring> // Para memcpy
#include <fstream> // Para basic_ostream, operator<<, endl, basic_... #include <fstream> // Para basic_ostream, operator<<, endl, basic_...
#include <iostream> // Para cerr #include <iostream> // Para cerr
#include <iterator> // Para istreambuf_iterator, operator== #include <iterator> // Para istreambuf_iterator, operator==
@@ -17,6 +18,7 @@
#include "core/resources/resource_helper.hpp" // Para ResourceHelper #include "core/resources/resource_helper.hpp" // Para ResourceHelper
#include "core/resources/resource_list.hpp" // Para Asset, AssetType #include "core/resources/resource_list.hpp" // Para Asset, AssetType
#include "game/options.hpp" // Para Options, options, OptionsVideo, Border #include "game/options.hpp" // Para Options, options, OptionsVideo, Border
#include "game/ui/console.hpp" // Para Console
#include "game/ui/notifier.hpp" // Para Notifier #include "game/ui/notifier.hpp" // Para Notifier
// [SINGLETON] // [SINGLETON]
@@ -42,13 +44,14 @@ Screen::Screen()
: palettes_(Resource::List::get()->getListByType(Resource::List::Type::PALETTE)) { : palettes_(Resource::List::get()->getListByType(Resource::List::Type::PALETTE)) {
// Arranca SDL VIDEO, crea la ventana y el renderizador // Arranca SDL VIDEO, crea la ventana y el renderizador
initSDLVideo(); initSDLVideo();
if (Options::video.fullscreen) { if (Options::video.fullscreen) { SDL_HideCursor(); }
SDL_HideCursor();
} // Calcular tamaños y hacer .resize() de los buffers de píxeles
adjustWindowSize();
adjustRenderLogicalSize();
// Ajusta los tamaños // Ajusta los tamaños
game_surface_dstrect_ = {.x = Options::video.border.width, .y = Options::video.border.height, .w = Options::game.width, .h = Options::game.height}; game_surface_dstrect_ = {.x = Options::video.border.width, .y = Options::video.border.height, .w = Options::game.width, .h = Options::game.height};
// adjustWindowSize();
current_palette_ = findPalette(Options::video.palette); current_palette_ = findPalette(Options::video.palette);
// Define el color del borde para el modo de pantalla completa // Define el color del borde para el modo de pantalla completa
@@ -87,6 +90,10 @@ Screen::Screen()
border_surface_->setPalette(initial_palette); border_surface_->setPalette(initial_palette);
border_surface_->clear(border_color_); border_surface_->clear(border_color_);
// Cachear el color ARGB inicial del borde (borde sólido por defecto)
border_surface_->toARGBBuffer(border_pixel_buffer_.data());
border_argb_color_ = border_pixel_buffer_[0];
// Establece la surface que actuará como renderer para recibir las llamadas a render() // Establece la surface que actuará como renderer para recibir las llamadas a render()
renderer_surface_ = std::make_shared<std::shared_ptr<Surface>>(game_surface_); renderer_surface_ = std::make_shared<std::shared_ptr<Surface>>(game_surface_);
@@ -190,6 +197,11 @@ auto Screen::incWindowZoom() -> bool {
void Screen::setBorderColor(Uint8 color) { void Screen::setBorderColor(Uint8 color) {
border_color_ = color; border_color_ = color;
border_surface_->clear(border_color_); border_surface_->clear(border_color_);
// Actualizar caché ARGB del borde sólido (ocurre una vez por habitación, no cada frame)
border_surface_->toARGBBuffer(border_pixel_buffer_.data());
border_argb_color_ = border_pixel_buffer_[0];
border_is_solid_ = true;
} }
// Cambia entre borde visible y no visible // Cambia entre borde visible y no visible
@@ -204,6 +216,9 @@ void Screen::renderNotifications() const {
if (notifications_enabled_) { if (notifications_enabled_) {
Notifier::get()->render(); Notifier::get()->render();
} }
if (Console::get() != nullptr) {
Console::get()->render();
}
} }
// Cambia el estado del PostFX // Cambia el estado del PostFX
@@ -236,6 +251,9 @@ void Screen::reloadPostFX() {
void Screen::update(float delta_time) { void Screen::update(float delta_time) {
fps_.calculate(SDL_GetTicks()); fps_.calculate(SDL_GetTicks());
Notifier::get()->update(delta_time); Notifier::get()->update(delta_time);
if (Console::get() != nullptr) {
Console::get()->update(delta_time);
}
Mouse::updateCursorVisibility(); Mouse::updateCursorVisibility();
} }
@@ -244,21 +262,28 @@ void Screen::adjustWindowSize() {
window_width_ = Options::game.width + (Options::video.border.enabled ? Options::video.border.width * 2 : 0); window_width_ = Options::game.width + (Options::video.border.enabled ? Options::video.border.width * 2 : 0);
window_height_ = Options::game.height + (Options::video.border.enabled ? Options::video.border.height * 2 : 0); window_height_ = Options::game.height + (Options::video.border.enabled ? Options::video.border.height * 2 : 0);
// Establece el nuevo tamaño // Reservamos memoria una sola vez.
// Si el buffer es más pequeño que la superficie, crash asegurado.
border_pixel_buffer_.resize(static_cast<size_t>(window_width_ * window_height_));
game_pixel_buffer_.resize(static_cast<size_t>(Options::game.width * Options::game.height));
// border_pixel_buffer_ es el buffer que se sube a la GPU (tamaño total ventana).
if (Options::video.border.enabled) {
border_pixel_buffer_.resize(static_cast<size_t>(window_width_ * window_height_));
}
// Lógica de centrado y redimensionado de ventana SDL
if (static_cast<int>(Options::video.fullscreen) == 0) { if (static_cast<int>(Options::video.fullscreen) == 0) {
int old_width; int old_w, old_h;
int old_height; SDL_GetWindowSize(window_, &old_w, &old_h);
SDL_GetWindowSize(window_, &old_width, &old_height); int old_x, old_y;
SDL_GetWindowPosition(window_, &old_x, &old_y);
int old_pos_x; const int NEW_X = old_x + ((old_w - (window_width_ * Options::window.zoom)) / 2);
int old_pos_y; const int NEW_Y = old_y + ((old_h - (window_height_ * Options::window.zoom)) / 2);
SDL_GetWindowPosition(window_, &old_pos_x, &old_pos_y);
const int NEW_POS_X = old_pos_x + ((old_width - (window_width_ * Options::window.zoom)) / 2);
const int NEW_POS_Y = old_pos_y + ((old_height - (window_height_ * Options::window.zoom)) / 2);
SDL_SetWindowSize(window_, window_width_ * Options::window.zoom, window_height_ * Options::window.zoom); SDL_SetWindowSize(window_, window_width_ * Options::window.zoom, window_height_ * Options::window.zoom);
SDL_SetWindowPosition(window_, std::max(NEW_POS_X, WINDOWS_DECORATIONS), std::max(NEW_POS_Y, 0)); SDL_SetWindowPosition(window_, std::max(NEW_X, WINDOWS_DECORATIONS), std::max(NEW_Y, 0));
} }
} }
@@ -294,7 +319,7 @@ void Screen::previousPalette() {
} }
// Establece la paleta // Establece la paleta
void Screen::setPalete() { void Screen::setPalete() { // NOLINT(readability-convert-member-functions-to-static)
game_surface_->loadPalette(Resource::Cache::get()->getPalette(palettes_.at(current_palette_))); game_surface_->loadPalette(Resource::Cache::get()->getPalette(palettes_.at(current_palette_)));
border_surface_->loadPalette(Resource::Cache::get()->getPalette(palettes_.at(current_palette_))); border_surface_->loadPalette(Resource::Cache::get()->getPalette(palettes_.at(current_palette_)));
@@ -308,6 +333,12 @@ void Screen::setPalete() {
// Convertir a mayúsculas // Convertir a mayúsculas
std::ranges::transform(Options::video.palette, Options::video.palette.begin(), ::toupper); std::ranges::transform(Options::video.palette, Options::video.palette.begin(), ::toupper);
// Actualizar caché si el borde es sólido (la paleta cambia el valor ARGB del color)
if (border_is_solid_) {
border_surface_->toARGBBuffer(border_pixel_buffer_.data());
border_argb_color_ = border_pixel_buffer_[0];
}
} }
// Extrae los nombres de las paletas // Extrae los nombres de las paletas
@@ -318,7 +349,7 @@ void Screen::processPaletteList() {
} }
// Copia la surface a la textura // Copia la surface a la textura
void Screen::surfaceToTexture() { void Screen::surfaceToTexture() { // NOLINT(readability-convert-member-functions-to-static)
if (Options::video.border.enabled) { if (Options::video.border.enabled) {
border_surface_->copyToTexture(renderer_, border_texture_); border_surface_->copyToTexture(renderer_, border_texture_);
game_surface_->copyToTexture(renderer_, border_texture_, nullptr, &game_surface_dstrect_); game_surface_->copyToTexture(renderer_, border_texture_, nullptr, &game_surface_dstrect_);
@@ -329,44 +360,59 @@ void Screen::surfaceToTexture() {
// Copia la textura al renderizador (o hace el present GPU) // Copia la textura al renderizador (o hace el present GPU)
void Screen::textureToRenderer() { void Screen::textureToRenderer() {
SDL_Texture* texture_to_render = Options::video.border.enabled ? border_texture_ : game_texture_;
if (shader_backend_ && shader_backend_->isHardwareAccelerated()) { if (shader_backend_ && shader_backend_->isHardwareAccelerated()) {
// ---- SDL3 GPU path: convertir Surface → ARGB → upload → PostFX/pass-through → present ---- const int GAME_W = Options::game.width;
if (Options::video.border.enabled) { const int GAME_H = Options::game.height;
// El border_surface_ solo tiene el color de borde; hay que componer encima el game_surface_
const int BORDER_W = static_cast<int>(border_surface_->getWidth());
const int BORDER_H = static_cast<int>(border_surface_->getHeight());
pixel_buffer_.resize(static_cast<size_t>(BORDER_W * BORDER_H));
border_surface_->toARGBBuffer(pixel_buffer_.data());
// Compositar game_surface_ en la posición correcta dentro del buffer if (Options::video.border.enabled) {
const int GAME_W = static_cast<int>(game_surface_->getWidth()); const int BORDER_W = window_width_;
const int GAME_H = static_cast<int>(game_surface_->getHeight()); const int BORDER_H = window_height_;
const int OFF_X = static_cast<int>(game_surface_dstrect_.x); const int OFF_X = static_cast<int>(game_surface_dstrect_.x);
const int OFF_Y = static_cast<int>(game_surface_dstrect_.y); const int OFF_Y = static_cast<int>(game_surface_dstrect_.y);
std::vector<Uint32> game_pixels(static_cast<size_t>(GAME_W * GAME_H));
game_surface_->toARGBBuffer(game_pixels.data()); if (border_is_solid_) {
for (int y = 0; y < GAME_H; ++y) { // Path A: borde sólido (gameplay normal)
for (int x = 0; x < GAME_W; ++x) { // Rellena solo el marco con el color cacheado — sin lookups de paleta.
pixel_buffer_[static_cast<size_t>(((OFF_Y + y) * BORDER_W) + (OFF_X + x))] = game_pixels[static_cast<size_t>((y * GAME_W) + x)]; // El área central (juego) se deja sin tocar; el overlay la sobreescribe igualmente.
// Franjas superior e inferior (ancho completo)
std::fill_n(border_pixel_buffer_.data(), OFF_Y * BORDER_W, border_argb_color_);
std::fill_n(&border_pixel_buffer_[(OFF_Y + GAME_H) * BORDER_W],
(BORDER_H - OFF_Y - GAME_H) * BORDER_W, border_argb_color_);
// Columnas laterales en las filas del área de juego
for (int y = OFF_Y; y < OFF_Y + GAME_H; ++y) {
std::fill_n(&border_pixel_buffer_[y * BORDER_W], OFF_X, border_argb_color_);
std::fill_n(&border_pixel_buffer_[y * BORDER_W + OFF_X + GAME_W],
BORDER_W - OFF_X - GAME_W, border_argb_color_);
} }
} else {
// Path B: borde dinámico (escena de carga — bandas de colores animadas)
// Conversión completa: la escena modifica border_surface_ cada frame
border_surface_->toARGBBuffer(border_pixel_buffer_.data());
} }
shader_backend_->uploadPixels(pixel_buffer_.data(), BORDER_W, BORDER_H);
// Overlay del juego sobre el centro del buffer (ambos paths)
game_surface_->toARGBBuffer(game_pixel_buffer_.data());
for (int y = 0; y < GAME_H; ++y) {
const Uint32* src = &game_pixel_buffer_[y * GAME_W];
Uint32* dst = &border_pixel_buffer_[(OFF_Y + y) * BORDER_W + OFF_X];
std::memcpy(dst, src, GAME_W * sizeof(Uint32));
}
shader_backend_->uploadPixels(border_pixel_buffer_.data(), BORDER_W, BORDER_H);
} else { } else {
const int GAME_W = static_cast<int>(game_surface_->getWidth()); // Caso sin borde: subida directa simplificada
const int GAME_H = static_cast<int>(game_surface_->getHeight()); game_surface_->toARGBBuffer(game_pixel_buffer_.data());
pixel_buffer_.resize(static_cast<size_t>(GAME_W * GAME_H)); shader_backend_->uploadPixels(game_pixel_buffer_.data(), GAME_W, GAME_H);
game_surface_->toARGBBuffer(pixel_buffer_.data());
shader_backend_->uploadPixels(pixel_buffer_.data(), GAME_W, GAME_H);
} }
shader_backend_->render(); shader_backend_->render();
} else { } else {
// ---- SDL_Renderer path (fallback / no-shader) ---- // Fallback SDL_Renderer (mantiene tu lógica de texturas SDL)
SDL_Texture* tex = Options::video.border.enabled ? border_texture_ : game_texture_;
SDL_SetRenderTarget(renderer_, nullptr); SDL_SetRenderTarget(renderer_, nullptr);
SDL_SetRenderDrawColor(renderer_, 0x00, 0x00, 0x00, 0xFF);
SDL_RenderClear(renderer_); SDL_RenderClear(renderer_);
SDL_RenderTexture(renderer_, texture_to_render, nullptr, nullptr); SDL_RenderTexture(renderer_, tex, nullptr, nullptr);
SDL_RenderPresent(renderer_); SDL_RenderPresent(renderer_);
} }
} }
@@ -380,7 +426,7 @@ void Screen::renderOverlays() {
} }
// Localiza la paleta dentro del vector de paletas // Localiza la paleta dentro del vector de paletas
auto Screen::findPalette(const std::string& name) -> size_t { auto Screen::findPalette(const std::string& name) -> size_t { // NOLINT(readability-convert-member-functions-to-static)
std::string upper_name = toUpper(name + ".pal"); std::string upper_name = toUpper(name + ".pal");
for (size_t i = 0; i < palettes_.size(); ++i) { for (size_t i = 0; i < palettes_.size(); ++i) {
@@ -393,18 +439,45 @@ auto Screen::findPalette(const std::string& name) -> size_t {
// Muestra información por pantalla // Muestra información por pantalla
void Screen::renderInfo() const { void Screen::renderInfo() const {
if (show_debug_info_ && (Resource::Cache::get() != nullptr)) { if (!show_fps_ || text_ == nullptr) {
auto text = Resource::Cache::get()->getText("smb2"); return;
auto color = static_cast<Uint8>(PaletteColor::YELLOW);
auto shadow = static_cast<Uint8>(PaletteColor::BLACK);
// FPS con sombra
const std::string FPS_TEXT = std::to_string(fps_.last_value) + " FPS";
const int FPS_X = Options::game.width - text->length(FPS_TEXT) - 1;
text->writeColored(FPS_X + 1, 1, FPS_TEXT, shadow);
text->writeColored(FPS_X, 0, FPS_TEXT, color);
} }
const int LINE_HEIGHT = text_->getCharacterSize() - 3;
const int X = 0;
int y = (Console::get() != nullptr) ? Console::get()->getVisibleHeight() : 0;
// FPS
const std::string FPS_TEXT = std::to_string(fps_.last_value) + " fps";
text_->write(X, y, FPS_TEXT);
y += LINE_HEIGHT;
// Driver GPU
text_->write(X, y, gpu_driver_.empty() ? "sdl" : gpu_driver_);
y += LINE_HEIGHT;
// Zoom de la ventana
const std::string ZOOM_TEXT = "zoom x" + std::to_string(Options::window.zoom);
text_->write(X, y, ZOOM_TEXT);
y += LINE_HEIGHT;
// PostFX enabled
const std::string POSTFX_TEXT = std::string("postfx ") + (Options::video.postfx ? "on" : "off");
text_->write(X, y, POSTFX_TEXT);
y += LINE_HEIGHT;
// PostFX preset
std::string preset_name = "-";
if (!Options::postfx_presets.empty()) {
preset_name = Options::postfx_presets[static_cast<size_t>(Options::current_postfx_preset)].name;
}
const std::string PRESET_TEXT = "preset " + preset_name;
text_->write(X, y, PRESET_TEXT);
y += LINE_HEIGHT;
// Supersampling enabled
const std::string SS_TEXT = std::string("ss ") + (Options::video.supersampling ? "on" : "off");
text_->write(X, y, SS_TEXT);
} }
// Limpia la game_surface_ // Limpia la game_surface_
@@ -428,8 +501,8 @@ void Screen::hide() { SDL_HideWindow(window_); }
// Establece la visibilidad de las notificaciones // Establece la visibilidad de las notificaciones
void Screen::setNotificationsEnabled(bool value) { notifications_enabled_ = value; } void Screen::setNotificationsEnabled(bool value) { notifications_enabled_ = value; }
// Activa / desactiva la información de debug // Activa / desactiva el contador de FPS
void Screen::toggleDebugInfo() { show_debug_info_ = !show_debug_info_; } void Screen::toggleFPS() { show_fps_ = !show_fps_; }
// Alterna entre activar y desactivar el escalado entero // Alterna entre activar y desactivar el escalado entero
void Screen::toggleIntegerScale() { void Screen::toggleIntegerScale() {
@@ -452,18 +525,45 @@ void Screen::toggleVSync() {
// Getters // Getters
auto Screen::getRenderer() -> SDL_Renderer* { return renderer_; } auto Screen::getRenderer() -> SDL_Renderer* { return renderer_; }
auto Screen::getRendererSurface() -> std::shared_ptr<Surface> { return (*renderer_surface_); } auto Screen::getRendererSurface() -> std::shared_ptr<Surface> { return (*renderer_surface_); }
auto Screen::getBorderSurface() -> std::shared_ptr<Surface> { return border_surface_; } auto Screen::getBorderSurface() -> std::shared_ptr<Surface> {
border_is_solid_ = false; // Modificación externa → modo borde dinámico
return border_surface_;
}
auto loadData(const std::string& filepath) -> std::vector<uint8_t> { auto loadData(const std::string& filepath) -> std::vector<uint8_t> {
// Load using ResourceHelper (supports both filesystem and pack) // Load using ResourceHelper (supports both filesystem and pack)
return Resource::Helper::loadFile(filepath); return Resource::Helper::loadFile(filepath);
} }
void Screen::setLinearUpscale(bool linear) {
if (shader_backend_ && shader_backend_->isHardwareAccelerated()) {
shader_backend_->setLinearUpscale(linear);
}
}
void Screen::setDownscaleAlgo(int algo) {
if (shader_backend_ && shader_backend_->isHardwareAccelerated()) {
shader_backend_->setDownscaleAlgo(algo);
}
}
// Activa/desactiva el supersampling global (Ctrl+F4)
void Screen::toggleSupersampling() {
Options::video.supersampling = !Options::video.supersampling;
if (Options::video.postfx && shader_backend_ && shader_backend_->isHardwareAccelerated()) {
applyCurrentPostFXPreset();
}
}
// Aplica los parámetros del preset actual al backend de shaders // Aplica los parámetros del preset actual al backend de shaders
void Screen::applyCurrentPostFXPreset() { void Screen::applyCurrentPostFXPreset() { // NOLINT(readability-convert-member-functions-to-static)
if (shader_backend_ && !Options::postfx_presets.empty()) { if (shader_backend_ && !Options::postfx_presets.empty()) {
const auto& p = Options::postfx_presets[static_cast<size_t>(Options::current_postfx_preset)]; const auto& p = Options::postfx_presets[static_cast<size_t>(Options::current_postfx_preset)];
Rendering::PostFXParams params{.vignette = p.vignette, .scanlines = p.scanlines, .chroma = p.chroma, .mask = p.mask, .gamma = p.gamma, .curvature = p.curvature, .bleeding = p.bleeding}; // Supersampling es un toggle global (Options::video.supersampling), no por preset.
// setOversample primero: puede recrear texturas antes de que setPostFXParams
// decida si hornear scanlines en CPU o aplicarlas en GPU.
shader_backend_->setOversample(Options::video.supersampling ? 3 : 1);
Rendering::PostFXParams params{.vignette = p.vignette, .scanlines = p.scanlines, .chroma = p.chroma, .mask = p.mask, .gamma = p.gamma, .curvature = p.curvature, .bleeding = p.bleeding, .flicker = p.flicker};
shader_backend_->setPostFXParams(params); shader_backend_->setPostFXParams(params);
} }
} }
@@ -478,6 +578,7 @@ void Screen::initShaders() {
shader_backend_ = std::make_unique<Rendering::SDL3GPUShader>(); shader_backend_ = std::make_unique<Rendering::SDL3GPUShader>();
} }
shader_backend_->init(window_, tex, "", ""); shader_backend_->init(window_, tex, "", "");
gpu_driver_ = shader_backend_->getDriverName();
// Propagar flags de vsync e integer scale al backend GPU // Propagar flags de vsync e integer scale al backend GPU
shader_backend_->setVSync(Options::video.vertical_sync); shader_backend_->setVSync(Options::video.vertical_sync);
@@ -492,7 +593,7 @@ void Screen::initShaders() {
} }
// Obtiene información sobre la pantalla // Obtiene información sobre la pantalla
void Screen::getDisplayInfo() { void Screen::getDisplayInfo() { // NOLINT(readability-convert-member-functions-to-static)
std::cout << "\n** VIDEO SYSTEM **\n"; std::cout << "\n** VIDEO SYSTEM **\n";
int num_displays = 0; int num_displays = 0;
@@ -597,10 +698,10 @@ auto Screen::initSDLVideo() -> bool {
} }
// Crea el objeto de texto // Crea el objeto de texto
void Screen::createText() { void Screen::createText() { // NOLINT(readability-convert-member-functions-to-static)
// Carga la surface de la fuente directamente del archivo // Carga la surface de la fuente directamente del archivo
auto surface = std::make_shared<Surface>(Resource::List::get()->get("aseprite.gif")); auto surface = std::make_shared<Surface>(Resource::List::get()->get("aseprite.gif"));
// Crea el objeto de texto (el constructor de Text carga el archivo text_file internamente) // Crea el objeto de texto (el constructor de Text carga el archivo text_file internamente)
text_ = std::make_shared<Text>(surface, Resource::List::get()->get("aseprite.txt")); text_ = std::make_shared<Text>(surface, Resource::List::get()->get("aseprite.fnt"));
} }

View File

@@ -53,16 +53,19 @@ class Screen {
void toggleBorder(); // Cambia entre borde visible y no visible void toggleBorder(); // Cambia entre borde visible y no visible
// Paletas y PostFX // Paletas y PostFX
void nextPalette(); // Cambia a la siguiente paleta void nextPalette(); // Cambia a la siguiente paleta
void previousPalette(); // Cambia a la paleta anterior void previousPalette(); // Cambia a la paleta anterior
void setPalete(); // Establece la paleta actual void setPalete(); // Establece la paleta actual
void togglePostFX(); // Cambia el estado del PostFX void togglePostFX(); // Cambia el estado del PostFX
void reloadPostFX(); // Recarga el shader del preset actual sin toggle void toggleSupersampling(); // Activa/desactiva el supersampling global
void reloadPostFX(); // Recarga el shader del preset actual sin toggle
void setLinearUpscale(bool linear); // Upscale NEAREST (false) o LINEAR (true) en el paso SS
void setDownscaleAlgo(int algo); // 0=bilinear legacy, 1=Lanczos2, 2=Lanczos3
// Surfaces y notificaciones // Surfaces y notificaciones
void setRendererSurface(const std::shared_ptr<Surface>& surface = nullptr); // Establece el renderizador para las surfaces void setRendererSurface(const std::shared_ptr<Surface>& surface = nullptr); // Establece el renderizador para las surfaces
void setNotificationsEnabled(bool value); // Establece la visibilidad de las notificaciones void setNotificationsEnabled(bool value); // Establece la visibilidad de las notificaciones
void toggleDebugInfo(); // Activa o desactiva la información de debug void toggleFPS(); // Activa o desactiva el contador de FPS
// Getters // Getters
auto getRenderer() -> SDL_Renderer*; auto getRenderer() -> SDL_Renderer*;
@@ -138,6 +141,14 @@ class Screen {
std::unique_ptr<Rendering::ShaderBackend> shader_backend_; // Backend de shaders (OpenGL/Metal/Vulkan) std::unique_ptr<Rendering::ShaderBackend> shader_backend_; // Backend de shaders (OpenGL/Metal/Vulkan)
std::shared_ptr<Text> text_; // Objeto para escribir texto std::shared_ptr<Text> text_; // Objeto para escribir texto
// Buffers persistentes para evitar .resize() cada frame
std::vector<Uint32> game_pixel_buffer_; // Textura de juego
std::vector<Uint32> border_pixel_buffer_; // Textura de borde (composición final borde+juego)
// Caché del borde sólido (gameplay normal)
bool border_is_solid_{true}; // true = borde de color sólido; false = borde dinámico (carga)
Uint32 border_argb_color_{0}; // Color ARGB pre-convertido del borde sólido
// Configuración de ventana y pantalla // Configuración de ventana y pantalla
int window_width_{0}; // Ancho de la pantalla o ventana int window_width_{0}; // Ancho de la pantalla o ventana
int window_height_{0}; // Alto de la pantalla o ventana int window_height_{0}; // Alto de la pantalla o ventana
@@ -154,12 +165,12 @@ class Screen {
DisplayMonitor display_monitor_; // Información de la pantalla DisplayMonitor display_monitor_; // Información de la pantalla
// Shaders // Shaders
std::string info_resolution_; // Texto con la información de la pantalla std::string info_resolution_; // Texto con la información de la pantalla
std::vector<Uint32> pixel_buffer_; // Buffer intermedio para SDL3GPU path (surface → ARGB) std::string gpu_driver_; // Nombre del driver GPU (SDL3GPU), capturado en initShaders()
#ifdef _DEBUG #ifdef _DEBUG
bool show_debug_info_{true}; // Indica si ha de mostrar la información de debug bool show_fps_{true}; // Indica si ha de mostrar el contador de FPS
#else #else
bool show_debug_info_{false}; // Indica si ha de mostrar la información de debug bool show_fps_{false}; // Indica si ha de mostrar el contador de FPS
#endif #endif
}; };

View File

@@ -0,0 +1,360 @@
#pragma once
#include <cstdint>
#include <cstddef>
static const uint8_t kdownscale_frag_spv[] = {
0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x0b, 0x00, 0x0d, 0x00,
0xb1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00,
0x01, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x32, 0x00, 0x00, 0x00,
0x0b, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00, 0x47, 0x4c, 0x53, 0x4c,
0x2e, 0x73, 0x74, 0x64, 0x2e, 0x34, 0x35, 0x30, 0x00, 0x00, 0x00, 0x00,
0x0e, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x0f, 0x00, 0x07, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x6d, 0x61, 0x69, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x41, 0x00, 0x00, 0x00,
0xa4, 0x00, 0x00, 0x00, 0x10, 0x00, 0x03, 0x00, 0x04, 0x00, 0x00, 0x00,
0x07, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00,
0xc2, 0x01, 0x00, 0x00, 0x04, 0x00, 0x0a, 0x00, 0x47, 0x4c, 0x5f, 0x47,
0x4f, 0x4f, 0x47, 0x4c, 0x45, 0x5f, 0x63, 0x70, 0x70, 0x5f, 0x73, 0x74,
0x79, 0x6c, 0x65, 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x64, 0x69, 0x72,
0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x00, 0x00, 0x04, 0x00, 0x08, 0x00,
0x47, 0x4c, 0x5f, 0x47, 0x4f, 0x4f, 0x47, 0x4c, 0x45, 0x5f, 0x69, 0x6e,
0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74,
0x69, 0x76, 0x65, 0x00, 0x05, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00,
0x6d, 0x61, 0x69, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x06, 0x00,
0x0b, 0x00, 0x00, 0x00, 0x6c, 0x61, 0x6e, 0x63, 0x7a, 0x6f, 0x73, 0x28,
0x66, 0x31, 0x3b, 0x66, 0x31, 0x3b, 0x00, 0x00, 0x05, 0x00, 0x03, 0x00,
0x09, 0x00, 0x00, 0x00, 0x74, 0x00, 0x00, 0x00, 0x05, 0x00, 0x03, 0x00,
0x0a, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, 0x05, 0x00, 0x03, 0x00,
0x1e, 0x00, 0x00, 0x00, 0x70, 0x74, 0x00, 0x00, 0x05, 0x00, 0x05, 0x00,
0x33, 0x00, 0x00, 0x00, 0x73, 0x72, 0x63, 0x5f, 0x73, 0x69, 0x7a, 0x65,
0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, 0x37, 0x00, 0x00, 0x00,
0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x00, 0x00, 0x05, 0x00, 0x03, 0x00,
0x3f, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00,
0x41, 0x00, 0x00, 0x00, 0x76, 0x5f, 0x75, 0x76, 0x00, 0x00, 0x00, 0x00,
0x05, 0x00, 0x04, 0x00, 0x45, 0x00, 0x00, 0x00, 0x70, 0x5f, 0x66, 0x6c,
0x6f, 0x6f, 0x72, 0x00, 0x05, 0x00, 0x03, 0x00, 0x48, 0x00, 0x00, 0x00,
0x61, 0x00, 0x00, 0x00, 0x05, 0x00, 0x07, 0x00, 0x49, 0x00, 0x00, 0x00,
0x44, 0x6f, 0x77, 0x6e, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x55, 0x6e, 0x69,
0x66, 0x6f, 0x72, 0x6d, 0x73, 0x00, 0x00, 0x00, 0x06, 0x00, 0x06, 0x00,
0x49, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x6c, 0x67, 0x6f,
0x72, 0x69, 0x74, 0x68, 0x6d, 0x00, 0x00, 0x00, 0x06, 0x00, 0x05, 0x00,
0x49, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x70, 0x61, 0x64, 0x30,
0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x05, 0x00, 0x49, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x70, 0x61, 0x64, 0x31, 0x00, 0x00, 0x00, 0x00,
0x06, 0x00, 0x05, 0x00, 0x49, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x70, 0x61, 0x64, 0x32, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x03, 0x00,
0x4b, 0x00, 0x00, 0x00, 0x75, 0x00, 0x00, 0x00, 0x05, 0x00, 0x03, 0x00,
0x54, 0x00, 0x00, 0x00, 0x77, 0x69, 0x6e, 0x00, 0x05, 0x00, 0x04, 0x00,
0x59, 0x00, 0x00, 0x00, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x00, 0x00, 0x00,
0x05, 0x00, 0x05, 0x00, 0x5b, 0x00, 0x00, 0x00, 0x77, 0x65, 0x69, 0x67,
0x68, 0x74, 0x5f, 0x73, 0x75, 0x6d, 0x00, 0x00, 0x05, 0x00, 0x03, 0x00,
0x5c, 0x00, 0x00, 0x00, 0x6a, 0x00, 0x00, 0x00, 0x05, 0x00, 0x03, 0x00,
0x67, 0x00, 0x00, 0x00, 0x69, 0x00, 0x00, 0x00, 0x05, 0x00, 0x05, 0x00,
0x72, 0x00, 0x00, 0x00, 0x74, 0x61, 0x70, 0x5f, 0x63, 0x65, 0x6e, 0x74,
0x65, 0x72, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, 0x7d, 0x00, 0x00, 0x00,
0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x00, 0x00, 0x05, 0x00, 0x03, 0x00,
0x81, 0x00, 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00,
0x82, 0x00, 0x00, 0x00, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x00, 0x00, 0x00,
0x05, 0x00, 0x04, 0x00, 0x87, 0x00, 0x00, 0x00, 0x70, 0x61, 0x72, 0x61,
0x6d, 0x00, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, 0x8a, 0x00, 0x00, 0x00,
0x70, 0x61, 0x72, 0x61, 0x6d, 0x00, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00,
0x8e, 0x00, 0x00, 0x00, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x00, 0x00, 0x00,
0x05, 0x00, 0x05, 0x00, 0xa4, 0x00, 0x00, 0x00, 0x6f, 0x75, 0x74, 0x5f,
0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00,
0x37, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x47, 0x00, 0x04, 0x00, 0x37, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x41, 0x00, 0x00, 0x00,
0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x03, 0x00,
0x49, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00,
0x49, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x49, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x48, 0x00, 0x05, 0x00, 0x49, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x23, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00,
0x49, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00,
0x0c, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x4b, 0x00, 0x00, 0x00,
0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00,
0x4b, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x47, 0x00, 0x04, 0x00, 0xa4, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00,
0x21, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x16, 0x00, 0x03, 0x00, 0x06, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
0x20, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
0x06, 0x00, 0x00, 0x00, 0x21, 0x00, 0x05, 0x00, 0x08, 0x00, 0x00, 0x00,
0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
0x17, 0xb7, 0xd1, 0x38, 0x14, 0x00, 0x02, 0x00, 0x11, 0x00, 0x00, 0x00,
0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00,
0x00, 0x00, 0x80, 0x3f, 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00,
0x06, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0xdb, 0x0f, 0x49, 0x40,
0x17, 0x00, 0x04, 0x00, 0x31, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x32, 0x00, 0x00, 0x00,
0x07, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x19, 0x00, 0x09, 0x00,
0x34, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x03, 0x00,
0x35, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00,
0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00,
0x3b, 0x00, 0x04, 0x00, 0x36, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00, 0x39, 0x00, 0x00, 0x00,
0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00,
0x39, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x17, 0x00, 0x04, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x40, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00,
0x40, 0x00, 0x00, 0x00, 0x41, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x1e, 0x00, 0x06, 0x00, 0x49, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00,
0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
0x20, 0x00, 0x04, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x49, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x4a, 0x00, 0x00, 0x00,
0x4b, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00,
0x4c, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00,
0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x40, 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
0x51, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x20, 0x00, 0x04, 0x00,
0x53, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00,
0x17, 0x00, 0x04, 0x00, 0x57, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x58, 0x00, 0x00, 0x00,
0x07, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x07, 0x00,
0x57, 0x00, 0x00, 0x00, 0x5a, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00,
0x1c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00,
0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x7a, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x3f, 0x15, 0x00, 0x04, 0x00, 0x83, 0x00, 0x00, 0x00,
0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00,
0x83, 0x00, 0x00, 0x00, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x2b, 0x00, 0x04, 0x00, 0x83, 0x00, 0x00, 0x00, 0x8b, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x39, 0x00, 0x00, 0x00,
0x9f, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00,
0xa3, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00,
0x3b, 0x00, 0x04, 0x00, 0xa3, 0x00, 0x00, 0x00, 0xa4, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x07, 0x00, 0x57, 0x00, 0x00, 0x00,
0xaf, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00,
0x1c, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x36, 0x00, 0x05, 0x00,
0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00,
0x3b, 0x00, 0x04, 0x00, 0x32, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00,
0x07, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x32, 0x00, 0x00, 0x00,
0x3f, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00,
0x32, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
0x3b, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00,
0x07, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x53, 0x00, 0x00, 0x00,
0x54, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00,
0x58, 0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
0x3b, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x5b, 0x00, 0x00, 0x00,
0x07, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x53, 0x00, 0x00, 0x00,
0x5c, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00,
0x53, 0x00, 0x00, 0x00, 0x67, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
0x3b, 0x00, 0x04, 0x00, 0x32, 0x00, 0x00, 0x00, 0x72, 0x00, 0x00, 0x00,
0x07, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x32, 0x00, 0x00, 0x00,
0x7d, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00,
0x07, 0x00, 0x00, 0x00, 0x81, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
0x3b, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x82, 0x00, 0x00, 0x00,
0x07, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00,
0x87, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00,
0x07, 0x00, 0x00, 0x00, 0x8a, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
0x3b, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x8e, 0x00, 0x00, 0x00,
0x07, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x58, 0x00, 0x00, 0x00,
0xa7, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00,
0x35, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00,
0x64, 0x00, 0x04, 0x00, 0x34, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00,
0x38, 0x00, 0x00, 0x00, 0x67, 0x00, 0x05, 0x00, 0x3c, 0x00, 0x00, 0x00,
0x3d, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00,
0x6f, 0x00, 0x04, 0x00, 0x31, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00,
0x3d, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00, 0x33, 0x00, 0x00, 0x00,
0x3e, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x31, 0x00, 0x00, 0x00,
0x42, 0x00, 0x00, 0x00, 0x41, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00,
0x31, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00,
0x85, 0x00, 0x05, 0x00, 0x31, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00,
0x42, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00,
0x3f, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00,
0x31, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00,
0x0c, 0x00, 0x06, 0x00, 0x31, 0x00, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0x00,
0x3e, 0x00, 0x03, 0x00, 0x45, 0x00, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00,
0x41, 0x00, 0x05, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x4d, 0x00, 0x00, 0x00,
0x4b, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00,
0x39, 0x00, 0x00, 0x00, 0x4e, 0x00, 0x00, 0x00, 0x4d, 0x00, 0x00, 0x00,
0xaa, 0x00, 0x05, 0x00, 0x11, 0x00, 0x00, 0x00, 0x4f, 0x00, 0x00, 0x00,
0x4e, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0xa9, 0x00, 0x06, 0x00,
0x06, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00, 0x4f, 0x00, 0x00, 0x00,
0x50, 0x00, 0x00, 0x00, 0x51, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00,
0x48, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00,
0x06, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00,
0x6e, 0x00, 0x04, 0x00, 0x39, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00,
0x55, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00, 0x54, 0x00, 0x00, 0x00,
0x56, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00, 0x59, 0x00, 0x00, 0x00,
0x5a, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00, 0x5b, 0x00, 0x00, 0x00,
0x1c, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x39, 0x00, 0x00, 0x00,
0x5d, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x04, 0x00,
0x39, 0x00, 0x00, 0x00, 0x5e, 0x00, 0x00, 0x00, 0x5d, 0x00, 0x00, 0x00,
0x3e, 0x00, 0x03, 0x00, 0x5c, 0x00, 0x00, 0x00, 0x5e, 0x00, 0x00, 0x00,
0xf9, 0x00, 0x02, 0x00, 0x5f, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00,
0x5f, 0x00, 0x00, 0x00, 0xf6, 0x00, 0x04, 0x00, 0x61, 0x00, 0x00, 0x00,
0x62, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf9, 0x00, 0x02, 0x00,
0x63, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x63, 0x00, 0x00, 0x00,
0x3d, 0x00, 0x04, 0x00, 0x39, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00,
0x5c, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x39, 0x00, 0x00, 0x00,
0x65, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0xb3, 0x00, 0x05, 0x00,
0x11, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00,
0x65, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x04, 0x00, 0x66, 0x00, 0x00, 0x00,
0x60, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00,
0x60, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x39, 0x00, 0x00, 0x00,
0x68, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x04, 0x00,
0x39, 0x00, 0x00, 0x00, 0x69, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00,
0x3e, 0x00, 0x03, 0x00, 0x67, 0x00, 0x00, 0x00, 0x69, 0x00, 0x00, 0x00,
0xf9, 0x00, 0x02, 0x00, 0x6a, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00,
0x6a, 0x00, 0x00, 0x00, 0xf6, 0x00, 0x04, 0x00, 0x6c, 0x00, 0x00, 0x00,
0x6d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf9, 0x00, 0x02, 0x00,
0x6e, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x6e, 0x00, 0x00, 0x00,
0x3d, 0x00, 0x04, 0x00, 0x39, 0x00, 0x00, 0x00, 0x6f, 0x00, 0x00, 0x00,
0x67, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x39, 0x00, 0x00, 0x00,
0x70, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0xb3, 0x00, 0x05, 0x00,
0x11, 0x00, 0x00, 0x00, 0x71, 0x00, 0x00, 0x00, 0x6f, 0x00, 0x00, 0x00,
0x70, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x04, 0x00, 0x71, 0x00, 0x00, 0x00,
0x6b, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00,
0x6b, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x31, 0x00, 0x00, 0x00,
0x73, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00,
0x39, 0x00, 0x00, 0x00, 0x74, 0x00, 0x00, 0x00, 0x67, 0x00, 0x00, 0x00,
0x6f, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x75, 0x00, 0x00, 0x00,
0x74, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x39, 0x00, 0x00, 0x00,
0x76, 0x00, 0x00, 0x00, 0x5c, 0x00, 0x00, 0x00, 0x6f, 0x00, 0x04, 0x00,
0x06, 0x00, 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, 0x76, 0x00, 0x00, 0x00,
0x50, 0x00, 0x05, 0x00, 0x31, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00,
0x75, 0x00, 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, 0x81, 0x00, 0x05, 0x00,
0x31, 0x00, 0x00, 0x00, 0x79, 0x00, 0x00, 0x00, 0x73, 0x00, 0x00, 0x00,
0x78, 0x00, 0x00, 0x00, 0x50, 0x00, 0x05, 0x00, 0x31, 0x00, 0x00, 0x00,
0x7b, 0x00, 0x00, 0x00, 0x7a, 0x00, 0x00, 0x00, 0x7a, 0x00, 0x00, 0x00,
0x81, 0x00, 0x05, 0x00, 0x31, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00,
0x79, 0x00, 0x00, 0x00, 0x7b, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00,
0x72, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00,
0x31, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x72, 0x00, 0x00, 0x00,
0x3d, 0x00, 0x04, 0x00, 0x31, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00,
0x3f, 0x00, 0x00, 0x00, 0x83, 0x00, 0x05, 0x00, 0x31, 0x00, 0x00, 0x00,
0x80, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00,
0x3e, 0x00, 0x03, 0x00, 0x7d, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
0x41, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x85, 0x00, 0x00, 0x00,
0x7d, 0x00, 0x00, 0x00, 0x84, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00,
0x06, 0x00, 0x00, 0x00, 0x86, 0x00, 0x00, 0x00, 0x85, 0x00, 0x00, 0x00,
0x3e, 0x00, 0x03, 0x00, 0x82, 0x00, 0x00, 0x00, 0x86, 0x00, 0x00, 0x00,
0x3d, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00,
0x48, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00, 0x87, 0x00, 0x00, 0x00,
0x88, 0x00, 0x00, 0x00, 0x39, 0x00, 0x06, 0x00, 0x06, 0x00, 0x00, 0x00,
0x89, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x82, 0x00, 0x00, 0x00,
0x87, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00,
0x8c, 0x00, 0x00, 0x00, 0x7d, 0x00, 0x00, 0x00, 0x8b, 0x00, 0x00, 0x00,
0x3d, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x8d, 0x00, 0x00, 0x00,
0x8c, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00, 0x8a, 0x00, 0x00, 0x00,
0x8d, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
0x8f, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00,
0x8e, 0x00, 0x00, 0x00, 0x8f, 0x00, 0x00, 0x00, 0x39, 0x00, 0x06, 0x00,
0x06, 0x00, 0x00, 0x00, 0x90, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
0x8a, 0x00, 0x00, 0x00, 0x8e, 0x00, 0x00, 0x00, 0x85, 0x00, 0x05, 0x00,
0x06, 0x00, 0x00, 0x00, 0x91, 0x00, 0x00, 0x00, 0x89, 0x00, 0x00, 0x00,
0x90, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00, 0x81, 0x00, 0x00, 0x00,
0x91, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x35, 0x00, 0x00, 0x00,
0x92, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00,
0x31, 0x00, 0x00, 0x00, 0x93, 0x00, 0x00, 0x00, 0x72, 0x00, 0x00, 0x00,
0x3d, 0x00, 0x04, 0x00, 0x31, 0x00, 0x00, 0x00, 0x94, 0x00, 0x00, 0x00,
0x33, 0x00, 0x00, 0x00, 0x88, 0x00, 0x05, 0x00, 0x31, 0x00, 0x00, 0x00,
0x95, 0x00, 0x00, 0x00, 0x93, 0x00, 0x00, 0x00, 0x94, 0x00, 0x00, 0x00,
0x57, 0x00, 0x05, 0x00, 0x57, 0x00, 0x00, 0x00, 0x96, 0x00, 0x00, 0x00,
0x92, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00,
0x06, 0x00, 0x00, 0x00, 0x97, 0x00, 0x00, 0x00, 0x81, 0x00, 0x00, 0x00,
0x8e, 0x00, 0x05, 0x00, 0x57, 0x00, 0x00, 0x00, 0x98, 0x00, 0x00, 0x00,
0x96, 0x00, 0x00, 0x00, 0x97, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00,
0x57, 0x00, 0x00, 0x00, 0x99, 0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00,
0x81, 0x00, 0x05, 0x00, 0x57, 0x00, 0x00, 0x00, 0x9a, 0x00, 0x00, 0x00,
0x99, 0x00, 0x00, 0x00, 0x98, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00,
0x59, 0x00, 0x00, 0x00, 0x9a, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00,
0x06, 0x00, 0x00, 0x00, 0x9b, 0x00, 0x00, 0x00, 0x81, 0x00, 0x00, 0x00,
0x3d, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x9c, 0x00, 0x00, 0x00,
0x5b, 0x00, 0x00, 0x00, 0x81, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00,
0x9d, 0x00, 0x00, 0x00, 0x9c, 0x00, 0x00, 0x00, 0x9b, 0x00, 0x00, 0x00,
0x3e, 0x00, 0x03, 0x00, 0x5b, 0x00, 0x00, 0x00, 0x9d, 0x00, 0x00, 0x00,
0xf9, 0x00, 0x02, 0x00, 0x6d, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00,
0x6d, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x39, 0x00, 0x00, 0x00,
0x9e, 0x00, 0x00, 0x00, 0x67, 0x00, 0x00, 0x00, 0x80, 0x00, 0x05, 0x00,
0x39, 0x00, 0x00, 0x00, 0xa0, 0x00, 0x00, 0x00, 0x9e, 0x00, 0x00, 0x00,
0x9f, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00, 0x67, 0x00, 0x00, 0x00,
0xa0, 0x00, 0x00, 0x00, 0xf9, 0x00, 0x02, 0x00, 0x6a, 0x00, 0x00, 0x00,
0xf8, 0x00, 0x02, 0x00, 0x6c, 0x00, 0x00, 0x00, 0xf9, 0x00, 0x02, 0x00,
0x62, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x62, 0x00, 0x00, 0x00,
0x3d, 0x00, 0x04, 0x00, 0x39, 0x00, 0x00, 0x00, 0xa1, 0x00, 0x00, 0x00,
0x5c, 0x00, 0x00, 0x00, 0x80, 0x00, 0x05, 0x00, 0x39, 0x00, 0x00, 0x00,
0xa2, 0x00, 0x00, 0x00, 0xa1, 0x00, 0x00, 0x00, 0x9f, 0x00, 0x00, 0x00,
0x3e, 0x00, 0x03, 0x00, 0x5c, 0x00, 0x00, 0x00, 0xa2, 0x00, 0x00, 0x00,
0xf9, 0x00, 0x02, 0x00, 0x5f, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00,
0x61, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
0xa5, 0x00, 0x00, 0x00, 0x5b, 0x00, 0x00, 0x00, 0xba, 0x00, 0x05, 0x00,
0x11, 0x00, 0x00, 0x00, 0xa6, 0x00, 0x00, 0x00, 0xa5, 0x00, 0x00, 0x00,
0x1c, 0x00, 0x00, 0x00, 0xf7, 0x00, 0x03, 0x00, 0xa9, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x04, 0x00, 0xa6, 0x00, 0x00, 0x00,
0xa8, 0x00, 0x00, 0x00, 0xae, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00,
0xa8, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x57, 0x00, 0x00, 0x00,
0xaa, 0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00,
0x06, 0x00, 0x00, 0x00, 0xab, 0x00, 0x00, 0x00, 0x5b, 0x00, 0x00, 0x00,
0x50, 0x00, 0x07, 0x00, 0x57, 0x00, 0x00, 0x00, 0xac, 0x00, 0x00, 0x00,
0xab, 0x00, 0x00, 0x00, 0xab, 0x00, 0x00, 0x00, 0xab, 0x00, 0x00, 0x00,
0xab, 0x00, 0x00, 0x00, 0x88, 0x00, 0x05, 0x00, 0x57, 0x00, 0x00, 0x00,
0xad, 0x00, 0x00, 0x00, 0xaa, 0x00, 0x00, 0x00, 0xac, 0x00, 0x00, 0x00,
0x3e, 0x00, 0x03, 0x00, 0xa7, 0x00, 0x00, 0x00, 0xad, 0x00, 0x00, 0x00,
0xf9, 0x00, 0x02, 0x00, 0xa9, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00,
0xae, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00, 0xa7, 0x00, 0x00, 0x00,
0xaf, 0x00, 0x00, 0x00, 0xf9, 0x00, 0x02, 0x00, 0xa9, 0x00, 0x00, 0x00,
0xf8, 0x00, 0x02, 0x00, 0xa9, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00,
0x57, 0x00, 0x00, 0x00, 0xb0, 0x00, 0x00, 0x00, 0xa7, 0x00, 0x00, 0x00,
0x3e, 0x00, 0x03, 0x00, 0xa4, 0x00, 0x00, 0x00, 0xb0, 0x00, 0x00, 0x00,
0xfd, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00, 0x36, 0x00, 0x05, 0x00,
0x06, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x08, 0x00, 0x00, 0x00, 0x37, 0x00, 0x03, 0x00, 0x07, 0x00, 0x00, 0x00,
0x09, 0x00, 0x00, 0x00, 0x37, 0x00, 0x03, 0x00, 0x07, 0x00, 0x00, 0x00,
0x0a, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x0c, 0x00, 0x00, 0x00,
0x3b, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00,
0x07, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
0x0d, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x06, 0x00,
0x06, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00,
0x09, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00,
0x06, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
0xb8, 0x00, 0x05, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0xf7, 0x00, 0x03, 0x00,
0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x04, 0x00,
0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
0xf8, 0x00, 0x02, 0x00, 0x13, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x02, 0x00,
0x15, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x14, 0x00, 0x00, 0x00,
0x3d, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00,
0x09, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
0x18, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0xbe, 0x00, 0x05, 0x00,
0x11, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00,
0x18, 0x00, 0x00, 0x00, 0xf7, 0x00, 0x03, 0x00, 0x1b, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x04, 0x00, 0x19, 0x00, 0x00, 0x00,
0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00,
0x1a, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x02, 0x00, 0x1c, 0x00, 0x00, 0x00,
0xf8, 0x00, 0x02, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00,
0x06, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
0x85, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00,
0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00,
0x1e, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00,
0x06, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
0x3d, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00,
0x1e, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x06, 0x00, 0x06, 0x00, 0x00, 0x00,
0x24, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
0x23, 0x00, 0x00, 0x00, 0x85, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00,
0x25, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00,
0x3d, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00,
0x1e, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
0x27, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x88, 0x00, 0x05, 0x00,
0x06, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00,
0x27, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x06, 0x00, 0x06, 0x00, 0x00, 0x00,
0x29, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
0x28, 0x00, 0x00, 0x00, 0x85, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00,
0x2a, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00,
0x3d, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00,
0x1e, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
0x2c, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x85, 0x00, 0x05, 0x00,
0x06, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00,
0x2c, 0x00, 0x00, 0x00, 0x88, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00,
0x2e, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00,
0xfe, 0x00, 0x02, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x38, 0x00, 0x01, 0x00
};
static const size_t kdownscale_frag_spv_size = 4248;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -7,8 +7,10 @@
#include <cstring> // memcpy, strlen #include <cstring> // memcpy, strlen
#ifndef __APPLE__ #ifndef __APPLE__
#include "core/rendering/sdl3gpu/downscale_frag_spv.h"
#include "core/rendering/sdl3gpu/postfx_frag_spv.h" #include "core/rendering/sdl3gpu/postfx_frag_spv.h"
#include "core/rendering/sdl3gpu/postfx_vert_spv.h" #include "core/rendering/sdl3gpu/postfx_vert_spv.h"
#include "core/rendering/sdl3gpu/upscale_frag_spv.h"
#endif #endif
#ifdef __APPLE__ #ifdef __APPLE__
@@ -54,6 +56,10 @@ struct PostFXUniforms {
float gamma_strength; float gamma_strength;
float curvature; float curvature;
float bleeding; float bleeding;
float pixel_scale;
float time;
float oversample; // 1.0 = sin SS, 3.0 = 3× supersampling
float flicker; // 0 = off, 1 = phosphor flicker ~50 Hz
}; };
// YCbCr helpers for NTSC bleeding // YCbCr helpers for NTSC bleeding
@@ -98,23 +104,25 @@ fragment float4 postfx_fs(PostVOut in [[stage_in]],
// Muestra base // Muestra base
float3 base = scene.sample(samp, uv).rgb; float3 base = scene.sample(samp, uv).rgb;
// Sangrado NTSC — difuminado horizontal de crominancia // Sangrado NTSC — difuminado horizontal de crominancia.
// step = 1 pixel de juego en espacio UV (corrige SS: scene.get_width() = game_w * oversample).
float3 colour; float3 colour;
if (u.bleeding > 0.0f) { if (u.bleeding > 0.0f) {
float tw = float(scene.get_width()); float tw = float(scene.get_width());
float3 ycc = rgb_to_ycc(base); float step = u.oversample / tw; // 1 pixel lógico en UV
float3 ycc_l2 = rgb_to_ycc(scene.sample(samp, uv - float2(2.0f/tw, 0.0f)).rgb); float3 ycc = rgb_to_ycc(base);
float3 ycc_l1 = rgb_to_ycc(scene.sample(samp, uv - float2(1.0f/tw, 0.0f)).rgb); float3 ycc_l2 = rgb_to_ycc(scene.sample(samp, uv - float2(2.0f*step, 0.0f)).rgb);
float3 ycc_r1 = rgb_to_ycc(scene.sample(samp, uv + float2(1.0f/tw, 0.0f)).rgb); float3 ycc_l1 = rgb_to_ycc(scene.sample(samp, uv - float2(1.0f*step, 0.0f)).rgb);
float3 ycc_r2 = rgb_to_ycc(scene.sample(samp, uv + float2(2.0f/tw, 0.0f)).rgb); float3 ycc_r1 = rgb_to_ycc(scene.sample(samp, uv + float2(1.0f*step, 0.0f)).rgb);
float3 ycc_r2 = rgb_to_ycc(scene.sample(samp, uv + float2(2.0f*step, 0.0f)).rgb);
ycc.yz = (ycc_l2.yz + ycc_l1.yz*2.0f + ycc.yz*2.0f + ycc_r1.yz*2.0f + ycc_r2.yz) / 8.0f; ycc.yz = (ycc_l2.yz + ycc_l1.yz*2.0f + ycc.yz*2.0f + ycc_r1.yz*2.0f + ycc_r2.yz) / 8.0f;
colour = mix(base, ycc_to_rgb(ycc), u.bleeding); colour = mix(base, ycc_to_rgb(ycc), u.bleeding);
} else { } else {
colour = base; colour = base;
} }
// Aberración cromática // Aberración cromática (drift animado con time para efecto NTSC real)
float ca = u.chroma_strength * 0.005f; float ca = u.chroma_strength * 0.005f * (1.0f + 0.15f * sin(u.time * 7.3f));
colour.r = scene.sample(samp, uv + float2(ca, 0.0f)).r; colour.r = scene.sample(samp, uv + float2(ca, 0.0f)).r;
colour.b = scene.sample(samp, uv - float2(ca, 0.0f)).b; colour.b = scene.sample(samp, uv - float2(ca, 0.0f)).b;
@@ -124,14 +132,20 @@ fragment float4 postfx_fs(PostVOut in [[stage_in]],
colour = mix(colour, lin, u.gamma_strength); colour = mix(colour, lin, u.gamma_strength);
} }
// Scanlines // Scanlines — proporción 2/3 brillantes + 1/3 oscuras por fila lógica.
float texHeight = float(scene.get_height()); // Casos especiales: 1 subfila → sin efecto; 2 subfilas → 1+1 (50/50).
float scaleY = u.screen_height / texHeight; // Constantes ajustables:
float screenY = uv.y * u.screen_height; const float SCAN_DARK_RATIO = 0.333f; // fracción de subfilas oscuras (ps >= 3)
float posInRow = fmod(screenY, scaleY); const float SCAN_DARK_FLOOR = 0.42f; // multiplicador de brillo de subfilas oscuras
float scanLineDY = posInRow / scaleY - 0.5f; if (u.scanline_strength > 0.0f) {
float scan = max(1.0f - scanLineDY * scanLineDY * 6.0f, 0.12f) * 3.5f; float ps = max(1.0f, round(u.pixel_scale));
colour *= mix(1.0f, scan, u.scanline_strength); float frac_in_row = fract(uv.y * u.screen_height);
float row_pos = floor(frac_in_row * ps);
float bright_rows = (ps < 2.0f) ? ps : ((ps < 3.0f) ? 1.0f : floor(ps * (1.0f - SCAN_DARK_RATIO)));
float is_dark = step(bright_rows, row_pos);
float scan = mix(1.0f, SCAN_DARK_FLOOR, is_dark);
colour *= mix(1.0f, scan, u.scanline_strength);
}
if (u.gamma_strength > 0.0f) { if (u.gamma_strength > 0.0f) {
float3 enc = pow(colour, float3(1.0f/2.2f)); float3 enc = pow(colour, float3(1.0f/2.2f));
@@ -143,7 +157,8 @@ fragment float4 postfx_fs(PostVOut in [[stage_in]],
float vignette = 1.0f - dot(d, d) * u.vignette_strength; float vignette = 1.0f - dot(d, d) * u.vignette_strength;
colour *= clamp(vignette, 0.0f, 1.0f); colour *= clamp(vignette, 0.0f, 1.0f);
// Máscara de fósforo RGB // Máscara de fósforo RGB — después de scanlines (orden original):
// filas brillantes saturadas → máscara invisible, filas oscuras → RGB visible.
if (u.mask_strength > 0.0f) { if (u.mask_strength > 0.0f) {
float whichMask = fract(in.pos.x * 0.3333333f); float whichMask = fract(in.pos.x * 0.3333333f);
float3 mask = float3(0.80f); float3 mask = float3(0.80f);
@@ -153,9 +168,66 @@ fragment float4 postfx_fs(PostVOut in [[stage_in]],
colour = mix(colour, colour * mask, u.mask_strength); colour = mix(colour, colour * mask, u.mask_strength);
} }
// Parpadeo de fósforo CRT (~50 Hz)
if (u.flicker > 0.0f) {
float flicker_wave = sin(u.time * 100.0f) * 0.5f + 0.5f;
colour *= 1.0f - u.flicker * 0.04f * flicker_wave;
}
return float4(colour, 1.0f); return float4(colour, 1.0f);
} }
)"; )";
static const char* UPSCALE_FRAG_MSL = R"(
#include <metal_stdlib>
using namespace metal;
struct VertOut { float4 pos [[position]]; float2 uv; };
fragment float4 upscale_fs(VertOut in [[stage_in]],
texture2d<float> scene [[texture(0)]],
sampler smp [[sampler(0)]])
{
return scene.sample(smp, in.uv);
}
)";
static const char* DOWNSCALE_FRAG_MSL = R"(
#include <metal_stdlib>
using namespace metal;
struct VertOut { float4 pos [[position]]; float2 uv; };
struct DownscaleUniforms { int algorithm; float pad0; float pad1; float pad2; };
static float lanczos_w(float t, float a) {
t = abs(t);
if (t < 0.0001f) { return 1.0f; }
if (t >= a) { return 0.0f; }
const float PI = 3.14159265358979f;
float pt = PI * t;
return (a * sin(pt) * sin(pt / a)) / (pt * pt);
}
fragment float4 downscale_fs(VertOut in [[stage_in]],
texture2d<float> source [[texture(0)]],
sampler smp [[sampler(0)]],
constant DownscaleUniforms& u [[buffer(0)]])
{
float2 src_size = float2(source.get_width(), source.get_height());
float2 p = in.uv * src_size;
float2 p_floor = floor(p);
float a = (u.algorithm == 0) ? 2.0f : 3.0f;
int win = int(a);
float4 color = float4(0.0f);
float weight_sum = 0.0f;
for (int j = -win; j <= win; j++) {
for (int i = -win; i <= win; i++) {
float2 tap_center = p_floor + float2(float(i), float(j)) + 0.5f;
float2 offset = tap_center - p;
float w = lanczos_w(offset.x, a) * lanczos_w(offset.y, a);
color += source.sample(smp, tap_center / src_size) * w;
weight_sum += w;
}
}
return (weight_sum > 0.0f) ? (color / weight_sum) : float4(0.0f, 0.0f, 0.0f, 1.0f);
}
)";
// NOLINTEND(readability-identifier-naming) // NOLINTEND(readability-identifier-naming)
#endif // __APPLE__ #endif // __APPLE__
@@ -189,9 +261,10 @@ namespace Rendering {
float fw = 0.0F; float fw = 0.0F;
float fh = 0.0F; float fh = 0.0F;
SDL_GetTextureSize(texture, &fw, &fh); SDL_GetTextureSize(texture, &fw, &fh);
tex_width_ = static_cast<int>(fw); game_width_ = static_cast<int>(fw);
tex_height_ = static_cast<int>(fh); game_height_ = static_cast<int>(fh);
uniforms_.screen_height = fh; // Altura lógica del juego (no el swapchain físico) uniforms_.screen_height = static_cast<float>(game_height_);
uniforms_.oversample = static_cast<float>(oversample_);
// ---------------------------------------------------------------- // ----------------------------------------------------------------
// 1. Create GPU device (solo si no existe ya) // 1. Create GPU device (solo si no existe ya)
@@ -207,7 +280,8 @@ namespace Rendering {
SDL_Log("SDL3GPUShader: SDL_CreateGPUDevice failed: %s", SDL_GetError()); SDL_Log("SDL3GPUShader: SDL_CreateGPUDevice failed: %s", SDL_GetError());
return false; return false;
} }
SDL_Log("SDL3GPUShader: driver = %s", SDL_GetGPUDeviceDriver(device_)); driver_name_ = SDL_GetGPUDeviceDriver(device_);
SDL_Log("SDL3GPUShader: driver = %s", driver_name_.c_str());
// ---------------------------------------------------------------- // ----------------------------------------------------------------
// 2. Claim window (una sola vez — no liberar hasta destroy()) // 2. Claim window (una sola vez — no liberar hasta destroy())
@@ -222,15 +296,15 @@ namespace Rendering {
} }
// ---------------------------------------------------------------- // ----------------------------------------------------------------
// 3. Create scene texture (upload target + sampler source) // 3. Create scene texture (upload target, always game resolution)
// Format: B8G8R8A8_UNORM matches SDL ARGB8888 byte layout on LE // Format: B8G8R8A8_UNORM matches SDL ARGB8888 byte layout on LE
// ---------------------------------------------------------------- // ----------------------------------------------------------------
SDL_GPUTextureCreateInfo tex_info = {}; SDL_GPUTextureCreateInfo tex_info = {};
tex_info.type = SDL_GPU_TEXTURETYPE_2D; tex_info.type = SDL_GPU_TEXTURETYPE_2D;
tex_info.format = SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM; tex_info.format = SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM;
tex_info.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER; tex_info.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER;
tex_info.width = static_cast<Uint32>(tex_width_); tex_info.width = static_cast<Uint32>(game_width_);
tex_info.height = static_cast<Uint32>(tex_height_); tex_info.height = static_cast<Uint32>(game_height_);
tex_info.layer_count_or_depth = 1; tex_info.layer_count_or_depth = 1;
tex_info.num_levels = 1; tex_info.num_levels = 1;
scene_texture_ = SDL_CreateGPUTexture(device_, &tex_info); scene_texture_ = SDL_CreateGPUTexture(device_, &tex_info);
@@ -240,12 +314,15 @@ namespace Rendering {
return false; return false;
} }
// scaled_texture_ se creará en el primer render() una vez conocido el zoom de ventana
ss_factor_ = 0;
// ---------------------------------------------------------------- // ----------------------------------------------------------------
// 4. Create upload transfer buffer (CPU → GPU, size = w*h*4 bytes) // 4. Create upload transfer buffer (CPU → GPU, always game resolution)
// ---------------------------------------------------------------- // ----------------------------------------------------------------
SDL_GPUTransferBufferCreateInfo tb_info = {}; SDL_GPUTransferBufferCreateInfo tb_info = {};
tb_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD; tb_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
tb_info.size = static_cast<Uint32>(tex_width_ * tex_height_ * 4); tb_info.size = static_cast<Uint32>(game_width_ * game_height_ * 4);
upload_buffer_ = SDL_CreateGPUTransferBuffer(device_, &tb_info); upload_buffer_ = SDL_CreateGPUTransferBuffer(device_, &tb_info);
if (upload_buffer_ == nullptr) { if (upload_buffer_ == nullptr) {
SDL_Log("SDL3GPUShader: failed to create upload buffer: %s", SDL_GetError()); SDL_Log("SDL3GPUShader: failed to create upload buffer: %s", SDL_GetError());
@@ -254,7 +331,7 @@ namespace Rendering {
} }
// ---------------------------------------------------------------- // ----------------------------------------------------------------
// 5. Create nearest-neighbour sampler (retro pixel art) // 5. Create samplers: NEAREST (pixel art) + LINEAR (supersampling)
// ---------------------------------------------------------------- // ----------------------------------------------------------------
SDL_GPUSamplerCreateInfo samp_info = {}; SDL_GPUSamplerCreateInfo samp_info = {};
samp_info.min_filter = SDL_GPU_FILTER_NEAREST; samp_info.min_filter = SDL_GPU_FILTER_NEAREST;
@@ -270,6 +347,20 @@ namespace Rendering {
return false; return false;
} }
SDL_GPUSamplerCreateInfo lsamp_info = {};
lsamp_info.min_filter = SDL_GPU_FILTER_LINEAR;
lsamp_info.mag_filter = SDL_GPU_FILTER_LINEAR;
lsamp_info.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_NEAREST;
lsamp_info.address_mode_u = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
lsamp_info.address_mode_v = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
lsamp_info.address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
linear_sampler_ = SDL_CreateGPUSampler(device_, &lsamp_info);
if (linear_sampler_ == nullptr) {
SDL_Log("SDL3GPUShader: failed to create linear sampler: %s", SDL_GetError());
cleanup();
return false;
}
// ---------------------------------------------------------------- // ----------------------------------------------------------------
// 6. Create PostFX graphics pipeline // 6. Create PostFX graphics pipeline
// ---------------------------------------------------------------- // ----------------------------------------------------------------
@@ -279,7 +370,7 @@ namespace Rendering {
} }
is_initialized_ = true; is_initialized_ = true;
SDL_Log("SDL3GPUShader: initialized OK (%dx%d)", tex_width_, tex_height_); SDL_Log("SDL3GPUShader: initialized OK — game %dx%d, oversample %d", game_width_, game_height_, oversample_);
return true; return true;
} }
@@ -289,6 +380,7 @@ namespace Rendering {
auto SDL3GPUShader::createPipeline() -> bool { auto SDL3GPUShader::createPipeline() -> bool {
const SDL_GPUTextureFormat SWAPCHAIN_FMT = SDL_GetGPUSwapchainTextureFormat(device_, window_); const SDL_GPUTextureFormat SWAPCHAIN_FMT = SDL_GetGPUSwapchainTextureFormat(device_, window_);
// ---- PostFX pipeline (scene/scaled → swapchain) ----
#ifdef __APPLE__ #ifdef __APPLE__
SDL_GPUShader* vert = createShaderMSL(device_, POSTFX_VERT_MSL, "postfx_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0); SDL_GPUShader* vert = createShaderMSL(device_, POSTFX_VERT_MSL, "postfx_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
SDL_GPUShader* frag = createShaderMSL(device_, POSTFX_FRAG_MSL, "postfx_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1); SDL_GPUShader* frag = createShaderMSL(device_, POSTFX_FRAG_MSL, "postfx_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
@@ -328,14 +420,132 @@ namespace Rendering {
SDL_ReleaseGPUShader(device_, frag); SDL_ReleaseGPUShader(device_, frag);
if (pipeline_ == nullptr) { if (pipeline_ == nullptr) {
SDL_Log("SDL3GPUShader: pipeline creation failed: %s", SDL_GetError()); SDL_Log("SDL3GPUShader: PostFX pipeline creation failed: %s", SDL_GetError());
return false; return false;
} }
// ---- Upscale pipeline (scene → scaled_texture_, nearest) ----
#ifdef __APPLE__
SDL_GPUShader* uvert = createShaderMSL(device_, POSTFX_VERT_MSL, "postfx_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
SDL_GPUShader* ufrag = createShaderMSL(device_, UPSCALE_FRAG_MSL, "upscale_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 0);
#else
SDL_GPUShader* uvert = createShaderSPIRV(device_, kpostfx_vert_spv, kpostfx_vert_spv_size, "main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
SDL_GPUShader* ufrag = createShaderSPIRV(device_, kupscale_frag_spv, kupscale_frag_spv_size, "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 0);
#endif
if ((uvert == nullptr) || (ufrag == nullptr)) {
SDL_Log("SDL3GPUShader: failed to compile upscale shaders");
if (uvert != nullptr) { SDL_ReleaseGPUShader(device_, uvert); }
if (ufrag != nullptr) { SDL_ReleaseGPUShader(device_, ufrag); }
return false;
}
SDL_GPUColorTargetDescription upscale_color_target = {};
upscale_color_target.format = SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM;
upscale_color_target.blend_state = no_blend;
SDL_GPUGraphicsPipelineCreateInfo upscale_pipe_info = {};
upscale_pipe_info.vertex_shader = uvert;
upscale_pipe_info.fragment_shader = ufrag;
upscale_pipe_info.vertex_input_state = no_input;
upscale_pipe_info.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST;
upscale_pipe_info.target_info.num_color_targets = 1;
upscale_pipe_info.target_info.color_target_descriptions = &upscale_color_target;
upscale_pipeline_ = SDL_CreateGPUGraphicsPipeline(device_, &upscale_pipe_info);
SDL_ReleaseGPUShader(device_, uvert);
SDL_ReleaseGPUShader(device_, ufrag);
if (upscale_pipeline_ == nullptr) {
SDL_Log("SDL3GPUShader: upscale pipeline creation failed: %s", SDL_GetError());
return false;
}
// ---- PostFX offscreen pipeline (scaled_texture_ → postfx_texture_, B8G8R8A8) ----
// Mismos shaders que pipeline_ pero con formato de salida B8G8R8A8_UNORM para textura intermedia.
#ifdef __APPLE__
SDL_GPUShader* ofvert = createShaderMSL(device_, POSTFX_VERT_MSL, "postfx_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
SDL_GPUShader* offrag = createShaderMSL(device_, POSTFX_FRAG_MSL, "postfx_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
#else
SDL_GPUShader* ofvert = createShaderSPIRV(device_, kpostfx_vert_spv, kpostfx_vert_spv_size, "main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
SDL_GPUShader* offrag = createShaderSPIRV(device_, kpostfx_frag_spv, kpostfx_frag_spv_size, "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
#endif
if ((ofvert == nullptr) || (offrag == nullptr)) {
SDL_Log("SDL3GPUShader: failed to compile PostFX offscreen shaders");
if (ofvert != nullptr) { SDL_ReleaseGPUShader(device_, ofvert); }
if (offrag != nullptr) { SDL_ReleaseGPUShader(device_, offrag); }
return false;
}
SDL_GPUColorTargetDescription offscreen_color_target = {};
offscreen_color_target.format = SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM;
offscreen_color_target.blend_state = no_blend;
SDL_GPUGraphicsPipelineCreateInfo offscreen_pipe_info = {};
offscreen_pipe_info.vertex_shader = ofvert;
offscreen_pipe_info.fragment_shader = offrag;
offscreen_pipe_info.vertex_input_state = no_input;
offscreen_pipe_info.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST;
offscreen_pipe_info.target_info.num_color_targets = 1;
offscreen_pipe_info.target_info.color_target_descriptions = &offscreen_color_target;
postfx_offscreen_pipeline_ = SDL_CreateGPUGraphicsPipeline(device_, &offscreen_pipe_info);
SDL_ReleaseGPUShader(device_, ofvert);
SDL_ReleaseGPUShader(device_, offrag);
if (postfx_offscreen_pipeline_ == nullptr) {
SDL_Log("SDL3GPUShader: PostFX offscreen pipeline creation failed: %s", SDL_GetError());
return false;
}
// ---- Downscale pipeline (postfx_texture_ → swapchain, Lanczos) ----
#ifdef __APPLE__
SDL_GPUShader* dvert = createShaderMSL(device_, POSTFX_VERT_MSL, "postfx_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
SDL_GPUShader* dfrag = createShaderMSL(device_, DOWNSCALE_FRAG_MSL, "downscale_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
#else
SDL_GPUShader* dvert = createShaderSPIRV(device_, kpostfx_vert_spv, kpostfx_vert_spv_size, "main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
SDL_GPUShader* dfrag = createShaderSPIRV(device_, kdownscale_frag_spv, kdownscale_frag_spv_size, "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
#endif
if ((dvert == nullptr) || (dfrag == nullptr)) {
SDL_Log("SDL3GPUShader: failed to compile downscale shaders");
if (dvert != nullptr) { SDL_ReleaseGPUShader(device_, dvert); }
if (dfrag != nullptr) { SDL_ReleaseGPUShader(device_, dfrag); }
return false;
}
SDL_GPUColorTargetDescription downscale_color_target = {};
downscale_color_target.format = SWAPCHAIN_FMT;
downscale_color_target.blend_state = no_blend;
SDL_GPUGraphicsPipelineCreateInfo downscale_pipe_info = {};
downscale_pipe_info.vertex_shader = dvert;
downscale_pipe_info.fragment_shader = dfrag;
downscale_pipe_info.vertex_input_state = no_input;
downscale_pipe_info.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST;
downscale_pipe_info.target_info.num_color_targets = 1;
downscale_pipe_info.target_info.color_target_descriptions = &downscale_color_target;
downscale_pipeline_ = SDL_CreateGPUGraphicsPipeline(device_, &downscale_pipe_info);
SDL_ReleaseGPUShader(device_, dvert);
SDL_ReleaseGPUShader(device_, dfrag);
if (downscale_pipeline_ == nullptr) {
SDL_Log("SDL3GPUShader: downscale pipeline creation failed: %s", SDL_GetError());
return false;
}
return true; return true;
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// uploadPixels — copies ARGB8888 CPU pixels into the GPU transfer buffer // uploadPixels — copies ARGB8888 CPU pixels into the GPU transfer buffer.
// Con supersampling (oversample_ > 1) expande cada pixel del juego a un bloque
// oversample × oversample y hornea la scanline oscura en la última fila del bloque.
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
void SDL3GPUShader::uploadPixels(const Uint32* pixels, int width, int height) { void SDL3GPUShader::uploadPixels(const Uint32* pixels, int width, int height) {
if (!is_initialized_ || (upload_buffer_ == nullptr)) { return; } if (!is_initialized_ || (upload_buffer_ == nullptr)) { return; }
@@ -345,7 +555,10 @@ namespace Rendering {
SDL_Log("SDL3GPUShader: SDL_MapGPUTransferBuffer failed: %s", SDL_GetError()); SDL_Log("SDL3GPUShader: SDL_MapGPUTransferBuffer failed: %s", SDL_GetError());
return; return;
} }
// Copia directa — el upscale lo hace la GPU en el primer render pass
std::memcpy(mapped, pixels, static_cast<size_t>(width * height * 4)); std::memcpy(mapped, pixels, static_cast<size_t>(width * height * 4));
SDL_UnmapGPUTransferBuffer(device_, upload_buffer_); SDL_UnmapGPUTransferBuffer(device_, upload_buffer_);
} }
@@ -355,31 +568,64 @@ namespace Rendering {
void SDL3GPUShader::render() { void SDL3GPUShader::render() {
if (!is_initialized_) { return; } if (!is_initialized_) { return; }
// Paso 0: si SS activo, calcular el factor necesario según el zoom actual y recrear si cambió.
// Factor = primer múltiplo de 3 >= zoom (mín 3). Se recrea solo en saltos de factor.
if (oversample_ > 1 && game_height_ > 0) {
int win_w = 0;
int win_h = 0;
SDL_GetWindowSizeInPixels(window_, &win_w, &win_h);
const float ZOOM = static_cast<float>(win_h) / static_cast<float>(game_height_);
const int NEED_FACTOR = calcSsFactor(ZOOM);
if (NEED_FACTOR != ss_factor_) {
SDL_WaitForGPUIdle(device_);
recreateScaledTexture(NEED_FACTOR);
}
}
SDL_GPUCommandBuffer* cmd = SDL_AcquireGPUCommandBuffer(device_); SDL_GPUCommandBuffer* cmd = SDL_AcquireGPUCommandBuffer(device_);
if (cmd == nullptr) { if (cmd == nullptr) {
SDL_Log("SDL3GPUShader: SDL_AcquireGPUCommandBuffer failed: %s", SDL_GetError()); SDL_Log("SDL3GPUShader: SDL_AcquireGPUCommandBuffer failed: %s", SDL_GetError());
return; return;
} }
// ---- Copy pass: transfer buffer → scene texture ---- // ---- Copy pass: transfer buffer → scene texture (siempre a resolución del juego) ----
SDL_GPUCopyPass* copy = SDL_BeginGPUCopyPass(cmd); SDL_GPUCopyPass* copy = SDL_BeginGPUCopyPass(cmd);
if (copy != nullptr) { if (copy != nullptr) {
SDL_GPUTextureTransferInfo src = {}; SDL_GPUTextureTransferInfo src = {};
src.transfer_buffer = upload_buffer_; src.transfer_buffer = upload_buffer_;
src.offset = 0; src.offset = 0;
src.pixels_per_row = static_cast<Uint32>(tex_width_); src.pixels_per_row = static_cast<Uint32>(game_width_);
src.rows_per_layer = static_cast<Uint32>(tex_height_); src.rows_per_layer = static_cast<Uint32>(game_height_);
SDL_GPUTextureRegion dst = {}; SDL_GPUTextureRegion dst = {};
dst.texture = scene_texture_; dst.texture = scene_texture_;
dst.w = static_cast<Uint32>(tex_width_); dst.w = static_cast<Uint32>(game_width_);
dst.h = static_cast<Uint32>(tex_height_); dst.h = static_cast<Uint32>(game_height_);
dst.d = 1; dst.d = 1;
SDL_UploadToGPUTexture(copy, &src, &dst, false); SDL_UploadToGPUTexture(copy, &src, &dst, false);
SDL_EndGPUCopyPass(copy); SDL_EndGPUCopyPass(copy);
} }
// ---- Upscale pass: scene_texture_ → scaled_texture_ (NEAREST o LINEAR según linear_upscale_) ----
if (oversample_ > 1 && scaled_texture_ != nullptr && upscale_pipeline_ != nullptr) {
SDL_GPUColorTargetInfo upscale_target = {};
upscale_target.texture = scaled_texture_;
upscale_target.load_op = SDL_GPU_LOADOP_DONT_CARE;
upscale_target.store_op = SDL_GPU_STOREOP_STORE;
SDL_GPURenderPass* upass = SDL_BeginGPURenderPass(cmd, &upscale_target, 1, nullptr);
if (upass != nullptr) {
SDL_BindGPUGraphicsPipeline(upass, upscale_pipeline_);
SDL_GPUTextureSamplerBinding ubinding = {};
ubinding.texture = scene_texture_;
ubinding.sampler = (linear_upscale_ && linear_sampler_ != nullptr) ? linear_sampler_ : sampler_;
SDL_BindGPUFragmentSamplers(upass, 0, &ubinding, 1);
SDL_DrawGPUPrimitives(upass, 3, 1, 0, 0);
SDL_EndGPURenderPass(upass);
}
}
// ---- Acquire swapchain texture ---- // ---- Acquire swapchain texture ----
SDL_GPUTexture* swapchain = nullptr; SDL_GPUTexture* swapchain = nullptr;
Uint32 sw = 0; Uint32 sw = 0;
@@ -395,47 +641,117 @@ namespace Rendering {
return; return;
} }
// ---- Render pass: PostFX → swapchain ---- // ---- Calcular viewport (dimensiones lógicas del canvas, no de textura GPU) ----
SDL_GPUColorTargetInfo color_target = {}; float vx = 0.0F;
color_target.texture = swapchain; float vy = 0.0F;
color_target.load_op = SDL_GPU_LOADOP_CLEAR; float vw = 0.0F;
color_target.store_op = SDL_GPU_STOREOP_STORE; float vh = 0.0F;
color_target.clear_color = {.r = 0.0F, .g = 0.0F, .b = 0.0F, .a = 1.0F}; if (integer_scale_) {
const int SCALE = std::max(1, std::min(static_cast<int>(sw) / game_width_, static_cast<int>(sh) / game_height_));
vw = static_cast<float>(game_width_ * SCALE);
vh = static_cast<float>(game_height_ * SCALE);
} else {
const float SCALE = std::min(
static_cast<float>(sw) / static_cast<float>(game_width_),
static_cast<float>(sh) / static_cast<float>(game_height_));
vw = static_cast<float>(game_width_) * SCALE;
vh = static_cast<float>(game_height_) * SCALE;
}
vx = std::floor((static_cast<float>(sw) - vw) * 0.5F);
vy = std::floor((static_cast<float>(sh) - vh) * 0.5F);
SDL_GPURenderPass* pass = SDL_BeginGPURenderPass(cmd, &color_target, 1, nullptr); // pixel_scale: subpíxeles por pixel lógico.
if (pass != nullptr) { // Sin SS: vh/game_height (zoom de ventana).
SDL_BindGPUGraphicsPipeline(pass, pipeline_); // Con SS: ss_factor_ exacto (3, 6, 9...).
uniforms_.pixel_scale = (oversample_ > 1 && ss_factor_ > 0)
? static_cast<float>(ss_factor_)
: ((game_height_ > 0) ? (vh / static_cast<float>(game_height_)) : 1.0F);
uniforms_.time = static_cast<float>(SDL_GetTicks()) / 1000.0F;
uniforms_.oversample = (oversample_ > 1 && ss_factor_ > 0)
? static_cast<float>(ss_factor_)
: 1.0F;
// Calcular viewport para mantener relación de aspecto (letterbox o integer scale) // ---- Determinar si usar el path Lanczos (SS activo + algo seleccionado) ----
float vx = 0.0F; const bool USE_LANCZOS = (oversample_ > 1 && downscale_algo_ > 0
float vy = 0.0F; && scaled_texture_ != nullptr
float vw = 0.0F; && postfx_texture_ != nullptr
float vh = 0.0F; && postfx_offscreen_pipeline_ != nullptr
if (integer_scale_) { && downscale_pipeline_ != nullptr);
const int SCALE = std::max(1, std::min(static_cast<int>(sw) / tex_width_, static_cast<int>(sh) / tex_height_));
vw = static_cast<float>(tex_width_ * SCALE); if (USE_LANCZOS) {
vh = static_cast<float>(tex_height_ * SCALE); // ---- Pass A: PostFX → postfx_texture_ (full scaled size, sin viewport) ----
} else { SDL_GPUColorTargetInfo postfx_target = {};
const float SCALE = std::min( postfx_target.texture = postfx_texture_;
static_cast<float>(sw) / static_cast<float>(tex_width_), postfx_target.load_op = SDL_GPU_LOADOP_CLEAR;
static_cast<float>(sh) / static_cast<float>(tex_height_)); postfx_target.store_op = SDL_GPU_STOREOP_STORE;
vw = static_cast<float>(tex_width_) * SCALE; postfx_target.clear_color = {.r = 0.0F, .g = 0.0F, .b = 0.0F, .a = 1.0F};
vh = static_cast<float>(tex_height_) * SCALE;
SDL_GPURenderPass* ppass = SDL_BeginGPURenderPass(cmd, &postfx_target, 1, nullptr);
if (ppass != nullptr) {
SDL_BindGPUGraphicsPipeline(ppass, postfx_offscreen_pipeline_);
SDL_GPUTextureSamplerBinding pbinding = {};
pbinding.texture = scaled_texture_;
pbinding.sampler = sampler_; // NEAREST: 1:1 pass, efectos calculados analíticamente
SDL_BindGPUFragmentSamplers(ppass, 0, &pbinding, 1);
SDL_PushGPUFragmentUniformData(cmd, 0, &uniforms_, sizeof(PostFXUniforms));
SDL_DrawGPUPrimitives(ppass, 3, 1, 0, 0);
SDL_EndGPURenderPass(ppass);
} }
vx = std::floor((static_cast<float>(sw) - vw) * 0.5F);
vy = std::floor((static_cast<float>(sh) - vh) * 0.5F);
SDL_GPUViewport vp = {vx, vy, vw, vh, 0.0F, 1.0F};
SDL_SetGPUViewport(pass, &vp);
SDL_GPUTextureSamplerBinding binding = {}; // ---- Pass B: Downscale Lanczos → swapchain (con viewport/letterbox) ----
binding.texture = scene_texture_; SDL_GPUColorTargetInfo ds_target = {};
binding.sampler = sampler_; ds_target.texture = swapchain;
SDL_BindGPUFragmentSamplers(pass, 0, &binding, 1); ds_target.load_op = SDL_GPU_LOADOP_CLEAR;
ds_target.store_op = SDL_GPU_STOREOP_STORE;
ds_target.clear_color = {.r = 0.0F, .g = 0.0F, .b = 0.0F, .a = 1.0F};
SDL_PushGPUFragmentUniformData(cmd, 0, &uniforms_, sizeof(PostFXUniforms)); SDL_GPURenderPass* dpass = SDL_BeginGPURenderPass(cmd, &ds_target, 1, nullptr);
if (dpass != nullptr) {
SDL_BindGPUGraphicsPipeline(dpass, downscale_pipeline_);
SDL_GPUViewport vp = {.x = vx, .y = vy, .w = vw, .h = vh, .min_depth = 0.0F, .max_depth = 1.0F};
SDL_SetGPUViewport(dpass, &vp);
SDL_GPUTextureSamplerBinding dbinding = {};
dbinding.texture = postfx_texture_;
dbinding.sampler = sampler_; // NEAREST: el shader Lanczos hace su propia interpolación
SDL_BindGPUFragmentSamplers(dpass, 0, &dbinding, 1);
// algorithm: 0=Lanczos2, 1=Lanczos3 (downscale_algo_ es 1-based)
DownscaleUniforms downscale_u = {.algorithm = downscale_algo_ - 1, .pad0 = 0.0F, .pad1 = 0.0F, .pad2 = 0.0F};
SDL_PushGPUFragmentUniformData(cmd, 0, &downscale_u, sizeof(DownscaleUniforms));
SDL_DrawGPUPrimitives(dpass, 3, 1, 0, 0);
SDL_EndGPURenderPass(dpass);
}
} else {
// ---- Render pass: PostFX → swapchain directamente (bilinear, comportamiento original) ----
SDL_GPUColorTargetInfo color_target = {};
color_target.texture = swapchain;
color_target.load_op = SDL_GPU_LOADOP_CLEAR;
color_target.store_op = SDL_GPU_STOREOP_STORE;
color_target.clear_color = {.r = 0.0F, .g = 0.0F, .b = 0.0F, .a = 1.0F};
SDL_DrawGPUPrimitives(pass, 3, 1, 0, 0); SDL_GPURenderPass* pass = SDL_BeginGPURenderPass(cmd, &color_target, 1, nullptr);
SDL_EndGPURenderPass(pass); if (pass != nullptr) {
SDL_BindGPUGraphicsPipeline(pass, pipeline_);
SDL_GPUViewport vp = {.x = vx, .y = vy, .w = vw, .h = vh, .min_depth = 0.0F, .max_depth = 1.0F};
SDL_SetGPUViewport(pass, &vp);
// Con SS: leer de scaled_texture_ con LINEAR; sin SS: scene_texture_ con NEAREST.
SDL_GPUTexture* input_texture = (oversample_ > 1 && scaled_texture_ != nullptr)
? scaled_texture_
: scene_texture_;
SDL_GPUSampler* active_sampler = (oversample_ > 1 && linear_sampler_ != nullptr)
? linear_sampler_
: sampler_;
SDL_GPUTextureSamplerBinding binding = {};
binding.texture = input_texture;
binding.sampler = active_sampler;
SDL_BindGPUFragmentSamplers(pass, 0, &binding, 1);
SDL_PushGPUFragmentUniformData(cmd, 0, &uniforms_, sizeof(PostFXUniforms));
SDL_DrawGPUPrimitives(pass, 3, 1, 0, 0);
SDL_EndGPURenderPass(pass);
}
} }
SDL_SubmitGPUCommandBuffer(cmd); SDL_SubmitGPUCommandBuffer(cmd);
@@ -454,10 +770,31 @@ namespace Rendering {
SDL_ReleaseGPUGraphicsPipeline(device_, pipeline_); SDL_ReleaseGPUGraphicsPipeline(device_, pipeline_);
pipeline_ = nullptr; pipeline_ = nullptr;
} }
if (postfx_offscreen_pipeline_ != nullptr) {
SDL_ReleaseGPUGraphicsPipeline(device_, postfx_offscreen_pipeline_);
postfx_offscreen_pipeline_ = nullptr;
}
if (upscale_pipeline_ != nullptr) {
SDL_ReleaseGPUGraphicsPipeline(device_, upscale_pipeline_);
upscale_pipeline_ = nullptr;
}
if (downscale_pipeline_ != nullptr) {
SDL_ReleaseGPUGraphicsPipeline(device_, downscale_pipeline_);
downscale_pipeline_ = nullptr;
}
if (scene_texture_ != nullptr) { if (scene_texture_ != nullptr) {
SDL_ReleaseGPUTexture(device_, scene_texture_); SDL_ReleaseGPUTexture(device_, scene_texture_);
scene_texture_ = nullptr; scene_texture_ = nullptr;
} }
if (scaled_texture_ != nullptr) {
SDL_ReleaseGPUTexture(device_, scaled_texture_);
scaled_texture_ = nullptr;
}
if (postfx_texture_ != nullptr) {
SDL_ReleaseGPUTexture(device_, postfx_texture_);
postfx_texture_ = nullptr;
}
ss_factor_ = 0;
if (upload_buffer_ != nullptr) { if (upload_buffer_ != nullptr) {
SDL_ReleaseGPUTransferBuffer(device_, upload_buffer_); SDL_ReleaseGPUTransferBuffer(device_, upload_buffer_);
upload_buffer_ = nullptr; upload_buffer_ = nullptr;
@@ -466,6 +803,10 @@ namespace Rendering {
SDL_ReleaseGPUSampler(device_, sampler_); SDL_ReleaseGPUSampler(device_, sampler_);
sampler_ = nullptr; sampler_ = nullptr;
} }
if (linear_sampler_ != nullptr) {
SDL_ReleaseGPUSampler(device_, linear_sampler_);
linear_sampler_ = nullptr;
}
// device_ y el claim de la ventana se mantienen vivos // device_ y el claim de la ventana se mantienen vivos
} }
} }
@@ -510,7 +851,7 @@ namespace Rendering {
return shader; return shader;
} }
auto SDL3GPUShader::createShaderSPIRV(SDL_GPUDevice* device, auto SDL3GPUShader::createShaderSPIRV(SDL_GPUDevice* device, // NOLINT(readability-convert-member-functions-to-static)
const uint8_t* spv_code, const uint8_t* spv_code,
size_t spv_size, size_t spv_size,
const char* entrypoint, const char* entrypoint,
@@ -534,12 +875,15 @@ namespace Rendering {
void SDL3GPUShader::setPostFXParams(const PostFXParams& p) { void SDL3GPUShader::setPostFXParams(const PostFXParams& p) {
uniforms_.vignette_strength = p.vignette; uniforms_.vignette_strength = p.vignette;
uniforms_.scanline_strength = p.scanlines;
uniforms_.chroma_strength = p.chroma; uniforms_.chroma_strength = p.chroma;
uniforms_.mask_strength = p.mask; uniforms_.mask_strength = p.mask;
uniforms_.gamma_strength = p.gamma; uniforms_.gamma_strength = p.gamma;
uniforms_.curvature = p.curvature; uniforms_.curvature = p.curvature;
uniforms_.bleeding = p.bleeding; uniforms_.bleeding = p.bleeding;
uniforms_.flicker = p.flicker;
// Las scanlines siempre las aplica el shader PostFX en GPU.
uniforms_.scanline_strength = p.scanlines;
} }
void SDL3GPUShader::setVSync(bool vsync) { void SDL3GPUShader::setVSync(bool vsync) {
@@ -553,4 +897,144 @@ namespace Rendering {
integer_scale_ = integer_scale; integer_scale_ = integer_scale;
} }
// ---------------------------------------------------------------------------
// setOversample — cambia el factor SS; recrea texturas si ya está inicializado
// ---------------------------------------------------------------------------
void SDL3GPUShader::setOversample(int factor) {
const int NEW_FACTOR = std::max(1, factor);
if (NEW_FACTOR == oversample_) { return; }
oversample_ = NEW_FACTOR;
if (is_initialized_) {
reinitTexturesAndBuffer();
// scanline_strength se actualizará en el próximo setPostFXParams
}
}
void SDL3GPUShader::setLinearUpscale(bool linear) {
linear_upscale_ = linear;
}
void SDL3GPUShader::setDownscaleAlgo(int algo) {
downscale_algo_ = std::max(0, std::min(algo, 2));
}
// ---------------------------------------------------------------------------
// reinitTexturesAndBuffer — recrea scene_texture_, scaled_texture_ y
// upload_buffer_ con el factor oversample_ actual. No toca pipelines ni samplers.
// ---------------------------------------------------------------------------
auto SDL3GPUShader::reinitTexturesAndBuffer() -> bool {
if (device_ == nullptr) { return false; }
SDL_WaitForGPUIdle(device_);
if (scene_texture_ != nullptr) {
SDL_ReleaseGPUTexture(device_, scene_texture_);
scene_texture_ = nullptr;
}
// scaled_texture_ se libera aquí; se recreará en el primer render() con el factor correcto
if (scaled_texture_ != nullptr) {
SDL_ReleaseGPUTexture(device_, scaled_texture_);
scaled_texture_ = nullptr;
}
ss_factor_ = 0;
if (upload_buffer_ != nullptr) {
SDL_ReleaseGPUTransferBuffer(device_, upload_buffer_);
upload_buffer_ = nullptr;
}
uniforms_.screen_height = static_cast<float>(game_height_);
uniforms_.oversample = static_cast<float>(oversample_);
// scene_texture_: siempre a resolución del juego
SDL_GPUTextureCreateInfo tex_info = {};
tex_info.type = SDL_GPU_TEXTURETYPE_2D;
tex_info.format = SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM;
tex_info.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER;
tex_info.width = static_cast<Uint32>(game_width_);
tex_info.height = static_cast<Uint32>(game_height_);
tex_info.layer_count_or_depth = 1;
tex_info.num_levels = 1;
scene_texture_ = SDL_CreateGPUTexture(device_, &tex_info);
if (scene_texture_ == nullptr) {
SDL_Log("SDL3GPUShader: reinit — failed to create scene texture: %s", SDL_GetError());
return false;
}
// upload_buffer_: siempre a resolución del juego
SDL_GPUTransferBufferCreateInfo tb_info = {};
tb_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
tb_info.size = static_cast<Uint32>(game_width_ * game_height_ * 4);
upload_buffer_ = SDL_CreateGPUTransferBuffer(device_, &tb_info);
if (upload_buffer_ == nullptr) {
SDL_Log("SDL3GPUShader: reinit — failed to create upload buffer: %s", SDL_GetError());
SDL_ReleaseGPUTexture(device_, scene_texture_);
scene_texture_ = nullptr;
return false;
}
SDL_Log("SDL3GPUShader: reinit — scene %dx%d, SS %s (scaled se creará en render)",
game_width_,
game_height_,
oversample_ > 1 ? "on" : "off");
return true;
}
// ---------------------------------------------------------------------------
// calcSsFactor — primer múltiplo de 3 >= zoom, mínimo 3.
// Ejemplos: zoom 1,2,3 → 3; zoom 4,5,6 → 6; zoom 4.4 → 6; zoom 7,8,9 → 9.
// ---------------------------------------------------------------------------
auto SDL3GPUShader::calcSsFactor(float zoom) -> int {
const int MULTIPLE = 3;
const int n = static_cast<int>(std::ceil(zoom / static_cast<float>(MULTIPLE)));
return std::max(1, n) * MULTIPLE;
}
// ---------------------------------------------------------------------------
// recreateScaledTexture — libera y recrea scaled_texture_ para el factor dado.
// Llamar solo cuando device_ no esté ejecutando comandos (SDL_WaitForGPUIdle previo).
// ---------------------------------------------------------------------------
auto SDL3GPUShader::recreateScaledTexture(int factor) -> bool {
if (scaled_texture_ != nullptr) {
SDL_ReleaseGPUTexture(device_, scaled_texture_);
scaled_texture_ = nullptr;
}
if (postfx_texture_ != nullptr) {
SDL_ReleaseGPUTexture(device_, postfx_texture_);
postfx_texture_ = nullptr;
}
ss_factor_ = 0;
const int W = game_width_ * factor;
const int H = game_height_ * factor;
SDL_GPUTextureCreateInfo info = {};
info.type = SDL_GPU_TEXTURETYPE_2D;
info.format = SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM;
info.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER | SDL_GPU_TEXTUREUSAGE_COLOR_TARGET;
info.width = static_cast<Uint32>(W);
info.height = static_cast<Uint32>(H);
info.layer_count_or_depth = 1;
info.num_levels = 1;
scaled_texture_ = SDL_CreateGPUTexture(device_, &info);
if (scaled_texture_ == nullptr) {
SDL_Log("SDL3GPUShader: failed to create scaled texture %dx%d (factor %d): %s",
W, H, factor, SDL_GetError());
return false;
}
postfx_texture_ = SDL_CreateGPUTexture(device_, &info);
if (postfx_texture_ == nullptr) {
SDL_Log("SDL3GPUShader: failed to create postfx texture %dx%d (factor %d): %s",
W, H, factor, SDL_GetError());
SDL_ReleaseGPUTexture(device_, scaled_texture_);
scaled_texture_ = nullptr;
return false;
}
ss_factor_ = factor;
SDL_Log("SDL3GPUShader: scaled+postfx textures %dx%d (factor %d×)", W, H, factor);
return true;
}
} // namespace Rendering } // namespace Rendering

Some files were not shown because too many files have changed in this diff Show More