web-a2e 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.clangd +5 -0
- package/.mcp.json +12 -0
- package/CLAUDE.md +362 -0
- package/CMakeLists.txt +774 -0
- package/LICENSE +21 -0
- package/README.md +392 -0
- package/build-wasm/generated/roms.cpp +2447 -0
- package/docker-compose.staging.yml +9 -0
- package/docs/basic-rom-disassembly.md +6663 -0
- package/docs/softswitch-comparison.md +273 -0
- package/docs/thunderclock-debug.md +89 -0
- package/examples/cube.bas +72 -0
- package/examples/hello.s +55 -0
- package/examples/scroll.s +140 -0
- package/package.json +18 -0
- package/public/assets/apple-logo-old.png +0 -0
- package/public/assets/apple-logo.png +0 -0
- package/public/assets/drive-closed-light-on.png +0 -0
- package/public/assets/drive-closed.png +0 -0
- package/public/assets/drive-open-light-on.png +0 -0
- package/public/assets/drive-open.png +0 -0
- package/public/audio-worklet.js +82 -0
- package/public/disks/Apple DOS 3.3 January 1983.dsk +0 -0
- package/public/disks/ProDOS 2.4.3.po +0 -0
- package/public/disks/h32mb.2mg +0 -0
- package/public/disks/library.json +26 -0
- package/public/docs/llms/llm-assembler.txt +90 -0
- package/public/docs/llms/llm-basic-program.txt +256 -0
- package/public/docs/llms/llm-disk-drives.txt +72 -0
- package/public/docs/llms/llm-file-explorer.txt +50 -0
- package/public/docs/llms/llm-hard-drives.txt +80 -0
- package/public/docs/llms/llm-main.txt +51 -0
- package/public/docs/llms/llm-slot-configuration.txt +66 -0
- package/public/icons/icon-192.svg +4 -0
- package/public/icons/icon-512.svg +4 -0
- package/public/index.html +661 -0
- package/public/llms.txt +49 -0
- package/public/manifest.json +29 -0
- package/public/shaders/burnin.glsl +22 -0
- package/public/shaders/crt.glsl +706 -0
- package/public/shaders/edge.glsl +109 -0
- package/public/shaders/vertex.glsl +8 -0
- package/public/sw.js +186 -0
- package/roms/341-0027.bin +0 -0
- package/roms/341-0160-A-US-UK.bin +0 -0
- package/roms/341-0160-A.bin +0 -0
- package/roms/342-0273-A-US-UK.bin +0 -0
- package/roms/342-0349-B-C0-FF.bin +0 -0
- package/roms/Apple Mouse Interface Card ROM - 342-0270-C.bin +0 -0
- package/roms/Thunderclock Plus ROM.bin +0 -0
- package/scripts/generate_roms.sh +69 -0
- package/src/bindings/wasm_interface.cpp +1940 -0
- package/src/core/assembler/assembler.cpp +1239 -0
- package/src/core/assembler/assembler.hpp +115 -0
- package/src/core/audio/audio.cpp +160 -0
- package/src/core/audio/audio.hpp +81 -0
- package/src/core/basic/basic_detokenizer.cpp +436 -0
- package/src/core/basic/basic_detokenizer.hpp +41 -0
- package/src/core/basic/basic_tokenizer.cpp +286 -0
- package/src/core/basic/basic_tokenizer.hpp +26 -0
- package/src/core/basic/basic_tokens.hpp +295 -0
- package/src/core/cards/disk2_card.cpp +568 -0
- package/src/core/cards/disk2_card.hpp +316 -0
- package/src/core/cards/expansion_card.hpp +185 -0
- package/src/core/cards/mockingboard/ay8910.cpp +616 -0
- package/src/core/cards/mockingboard/ay8910.hpp +159 -0
- package/src/core/cards/mockingboard/via6522.cpp +530 -0
- package/src/core/cards/mockingboard/via6522.hpp +163 -0
- package/src/core/cards/mockingboard_card.cpp +312 -0
- package/src/core/cards/mockingboard_card.hpp +159 -0
- package/src/core/cards/mouse_card.cpp +654 -0
- package/src/core/cards/mouse_card.hpp +190 -0
- package/src/core/cards/smartport/block_device.cpp +202 -0
- package/src/core/cards/smartport/block_device.hpp +60 -0
- package/src/core/cards/smartport/smartport_card.cpp +603 -0
- package/src/core/cards/smartport/smartport_card.hpp +120 -0
- package/src/core/cards/thunderclock_card.cpp +237 -0
- package/src/core/cards/thunderclock_card.hpp +122 -0
- package/src/core/cpu/cpu6502.cpp +1609 -0
- package/src/core/cpu/cpu6502.hpp +203 -0
- package/src/core/debug/condition_evaluator.cpp +470 -0
- package/src/core/debug/condition_evaluator.hpp +87 -0
- package/src/core/disassembler/disassembler.cpp +552 -0
- package/src/core/disassembler/disassembler.hpp +171 -0
- package/src/core/disk-image/disk_image.hpp +267 -0
- package/src/core/disk-image/dsk_disk_image.cpp +827 -0
- package/src/core/disk-image/dsk_disk_image.hpp +204 -0
- package/src/core/disk-image/gcr_encoding.cpp +147 -0
- package/src/core/disk-image/gcr_encoding.hpp +78 -0
- package/src/core/disk-image/woz_disk_image.cpp +1049 -0
- package/src/core/disk-image/woz_disk_image.hpp +343 -0
- package/src/core/emulator.cpp +2126 -0
- package/src/core/emulator.hpp +434 -0
- package/src/core/filesystem/dos33.cpp +178 -0
- package/src/core/filesystem/dos33.hpp +66 -0
- package/src/core/filesystem/pascal.cpp +262 -0
- package/src/core/filesystem/pascal.hpp +87 -0
- package/src/core/filesystem/prodos.cpp +369 -0
- package/src/core/filesystem/prodos.hpp +119 -0
- package/src/core/input/keyboard.cpp +227 -0
- package/src/core/input/keyboard.hpp +111 -0
- package/src/core/mmu/mmu.cpp +1387 -0
- package/src/core/mmu/mmu.hpp +236 -0
- package/src/core/types.hpp +196 -0
- package/src/core/video/video.cpp +680 -0
- package/src/core/video/video.hpp +156 -0
- package/src/css/assembler-editor.css +1617 -0
- package/src/css/base.css +470 -0
- package/src/css/basic-debugger.css +791 -0
- package/src/css/basic-editor.css +792 -0
- package/src/css/controls.css +783 -0
- package/src/css/cpu-debugger.css +1413 -0
- package/src/css/debug-base.css +160 -0
- package/src/css/debug-windows.css +6455 -0
- package/src/css/disk-drives.css +406 -0
- package/src/css/documentation.css +392 -0
- package/src/css/file-explorer.css +867 -0
- package/src/css/hard-drive.css +180 -0
- package/src/css/layout.css +217 -0
- package/src/css/memory-windows.css +798 -0
- package/src/css/modals.css +510 -0
- package/src/css/monitor.css +425 -0
- package/src/css/release-notes.css +101 -0
- package/src/css/responsive.css +400 -0
- package/src/css/rule-builder.css +340 -0
- package/src/css/save-states.css +201 -0
- package/src/css/settings-windows.css +1231 -0
- package/src/css/window-switcher.css +150 -0
- package/src/js/agent/agent-manager.js +643 -0
- package/src/js/agent/agent-tools.js +293 -0
- package/src/js/agent/agent-version-tools.js +131 -0
- package/src/js/agent/assembler-tools.js +357 -0
- package/src/js/agent/basic-program-tools.js +894 -0
- package/src/js/agent/disk-tools.js +417 -0
- package/src/js/agent/file-explorer-tools.js +269 -0
- package/src/js/agent/index.js +13 -0
- package/src/js/agent/main-tools.js +222 -0
- package/src/js/agent/slot-tools.js +303 -0
- package/src/js/agent/smartport-tools.js +257 -0
- package/src/js/agent/window-tools.js +80 -0
- package/src/js/audio/audio-driver.js +417 -0
- package/src/js/audio/audio-worklet.js +85 -0
- package/src/js/audio/index.js +8 -0
- package/src/js/config/default-layout.js +34 -0
- package/src/js/config/version.js +8 -0
- package/src/js/data/apple2-rom-routines.js +577 -0
- package/src/js/debug/assembler-editor-window.js +2993 -0
- package/src/js/debug/basic-breakpoint-manager.js +529 -0
- package/src/js/debug/basic-program-parser.js +436 -0
- package/src/js/debug/basic-program-window.js +2594 -0
- package/src/js/debug/basic-variable-inspector.js +447 -0
- package/src/js/debug/breakpoint-manager.js +472 -0
- package/src/js/debug/cpu-debugger-window.js +2396 -0
- package/src/js/debug/index.js +22 -0
- package/src/js/debug/label-manager.js +238 -0
- package/src/js/debug/memory-browser-window.js +416 -0
- package/src/js/debug/memory-heat-map-window.js +481 -0
- package/src/js/debug/memory-map-window.js +206 -0
- package/src/js/debug/mockingboard-window.js +882 -0
- package/src/js/debug/mouse-card-window.js +355 -0
- package/src/js/debug/rule-builder-window.js +648 -0
- package/src/js/debug/soft-switch-window.js +458 -0
- package/src/js/debug/stack-viewer-window.js +221 -0
- package/src/js/debug/symbols.js +416 -0
- package/src/js/debug/trace-panel.js +291 -0
- package/src/js/debug/zero-page-watch-window.js +297 -0
- package/src/js/disk-manager/disk-drives-window.js +212 -0
- package/src/js/disk-manager/disk-operations.js +284 -0
- package/src/js/disk-manager/disk-persistence.js +301 -0
- package/src/js/disk-manager/disk-surface-renderer.js +388 -0
- package/src/js/disk-manager/drive-sounds.js +139 -0
- package/src/js/disk-manager/hard-drive-manager.js +481 -0
- package/src/js/disk-manager/hard-drive-persistence.js +187 -0
- package/src/js/disk-manager/hard-drive-window.js +57 -0
- package/src/js/disk-manager/index.js +890 -0
- package/src/js/display/display-settings-window.js +383 -0
- package/src/js/display/index.js +10 -0
- package/src/js/display/screen-window.js +342 -0
- package/src/js/display/webgl-renderer.js +705 -0
- package/src/js/file-explorer/disassembler.js +574 -0
- package/src/js/file-explorer/dos33.js +266 -0
- package/src/js/file-explorer/file-viewer.js +359 -0
- package/src/js/file-explorer/index.js +1261 -0
- package/src/js/file-explorer/prodos.js +549 -0
- package/src/js/file-explorer/utils.js +67 -0
- package/src/js/help/documentation-window.js +1096 -0
- package/src/js/help/index.js +10 -0
- package/src/js/help/release-notes-window.js +85 -0
- package/src/js/help/release-notes.js +612 -0
- package/src/js/input/gamepad-handler.js +176 -0
- package/src/js/input/index.js +12 -0
- package/src/js/input/input-handler.js +396 -0
- package/src/js/input/joystick-window.js +404 -0
- package/src/js/input/mouse-handler.js +99 -0
- package/src/js/input/text-selection.js +462 -0
- package/src/js/main.js +653 -0
- package/src/js/state/index.js +15 -0
- package/src/js/state/save-states-window.js +393 -0
- package/src/js/state/state-manager.js +409 -0
- package/src/js/state/state-persistence.js +218 -0
- package/src/js/ui/confirm.js +43 -0
- package/src/js/ui/disk-drive-positioner.js +347 -0
- package/src/js/ui/reminder-controller.js +129 -0
- package/src/js/ui/slot-configuration-window.js +560 -0
- package/src/js/ui/theme-manager.js +61 -0
- package/src/js/ui/toast.js +44 -0
- package/src/js/ui/ui-controller.js +897 -0
- package/src/js/ui/window-switcher.js +275 -0
- package/src/js/utils/basic-autocomplete.js +832 -0
- package/src/js/utils/basic-highlighting.js +473 -0
- package/src/js/utils/basic-tokenizer.js +153 -0
- package/src/js/utils/basic-tokens.js +117 -0
- package/src/js/utils/constants.js +28 -0
- package/src/js/utils/indexeddb-helper.js +225 -0
- package/src/js/utils/merlin-editor-support.js +905 -0
- package/src/js/utils/merlin-highlighting.js +551 -0
- package/src/js/utils/storage.js +125 -0
- package/src/js/utils/string-utils.js +19 -0
- package/src/js/utils/wasm-memory.js +54 -0
- package/src/js/windows/base-window.js +690 -0
- package/src/js/windows/index.js +9 -0
- package/src/js/windows/window-manager.js +375 -0
- package/tests/catch2/catch.hpp +17976 -0
- package/tests/common/basic_program_builder.cpp +119 -0
- package/tests/common/basic_program_builder.hpp +209 -0
- package/tests/common/disk_image_builder.cpp +444 -0
- package/tests/common/disk_image_builder.hpp +141 -0
- package/tests/common/test_helpers.hpp +118 -0
- package/tests/gcr/gcr-test.cpp +142 -0
- package/tests/integration/check-rom.js +70 -0
- package/tests/integration/compare-boot.js +239 -0
- package/tests/integration/crash-trace.js +102 -0
- package/tests/integration/disk-boot-test.js +264 -0
- package/tests/integration/memory-crash.js +108 -0
- package/tests/integration/nibble-read-test.js +249 -0
- package/tests/integration/phase-test.js +159 -0
- package/tests/integration/test_emulator.cpp +291 -0
- package/tests/integration/test_emulator_basic.cpp +91 -0
- package/tests/integration/test_emulator_debug.cpp +344 -0
- package/tests/integration/test_emulator_disk.cpp +153 -0
- package/tests/integration/test_emulator_state.cpp +163 -0
- package/tests/klaus/6502_functional_test.bin +0 -0
- package/tests/klaus/65C02_extended_opcodes_test.bin +0 -0
- package/tests/klaus/klaus_6502_test.cpp +184 -0
- package/tests/klaus/klaus_65c02_test.cpp +197 -0
- package/tests/thunderclock/thunderclock_mmu_test.cpp +304 -0
- package/tests/thunderclock/thunderclock_test.cpp +550 -0
- package/tests/unit/test_assembler.cpp +521 -0
- package/tests/unit/test_audio.cpp +196 -0
- package/tests/unit/test_ay8910.cpp +311 -0
- package/tests/unit/test_basic_detokenizer.cpp +265 -0
- package/tests/unit/test_basic_tokenizer.cpp +382 -0
- package/tests/unit/test_block_device.cpp +259 -0
- package/tests/unit/test_condition_evaluator.cpp +219 -0
- package/tests/unit/test_cpu6502.cpp +1301 -0
- package/tests/unit/test_cpu_addressing.cpp +361 -0
- package/tests/unit/test_cpu_cycle_counts.cpp +409 -0
- package/tests/unit/test_cpu_decimal.cpp +166 -0
- package/tests/unit/test_cpu_interrupts.cpp +285 -0
- package/tests/unit/test_disassembler.cpp +323 -0
- package/tests/unit/test_disk2_card.cpp +330 -0
- package/tests/unit/test_dos33.cpp +273 -0
- package/tests/unit/test_dsk_disk_image.cpp +315 -0
- package/tests/unit/test_expansion_card.cpp +178 -0
- package/tests/unit/test_gcr_encoding.cpp +232 -0
- package/tests/unit/test_keyboard.cpp +262 -0
- package/tests/unit/test_mmu.cpp +555 -0
- package/tests/unit/test_mmu_slots.cpp +323 -0
- package/tests/unit/test_mockingboard.cpp +352 -0
- package/tests/unit/test_mouse_card.cpp +386 -0
- package/tests/unit/test_pascal.cpp +248 -0
- package/tests/unit/test_prodos.cpp +259 -0
- package/tests/unit/test_smartport_card.cpp +321 -0
- package/tests/unit/test_thunderclock.cpp +354 -0
- package/tests/unit/test_via6522.cpp +323 -0
- package/tests/unit/test_video.cpp +319 -0
- package/tests/unit/test_woz_disk_image.cpp +257 -0
- package/vite.config.js +96 -0
- package/wiki/AI-Agent.md +372 -0
- package/wiki/Architecture-Overview.md +303 -0
- package/wiki/Audio-System.md +449 -0
- package/wiki/CPU-Emulation.md +477 -0
- package/wiki/Debugger.md +516 -0
- package/wiki/Disk-Drives.md +161 -0
- package/wiki/Disk-System-Internals.md +547 -0
- package/wiki/Display-Settings.md +88 -0
- package/wiki/Expansion-Slots.md +187 -0
- package/wiki/File-Explorer.md +259 -0
- package/wiki/Getting-Started.md +156 -0
- package/wiki/Home.md +69 -0
- package/wiki/Input-Devices.md +183 -0
- package/wiki/Keyboard-Shortcuts.md +158 -0
- package/wiki/Memory-System.md +364 -0
- package/wiki/Save-States.md +172 -0
- package/wiki/Video-Rendering.md +658 -0
|
@@ -0,0 +1,706 @@
|
|
|
1
|
+
// Fragment shader with comprehensive CRT effects
|
|
2
|
+
// Inspired by cool-retro-term (https://github.com/Swordfish90/cool-retro-term)
|
|
3
|
+
|
|
4
|
+
precision highp float;
|
|
5
|
+
|
|
6
|
+
uniform sampler2D u_texture;
|
|
7
|
+
uniform sampler2D u_burnInTexture;
|
|
8
|
+
uniform sampler2D u_selectionTexture;
|
|
9
|
+
uniform vec2 u_resolution;
|
|
10
|
+
uniform vec2 u_textureSize;
|
|
11
|
+
uniform float u_time;
|
|
12
|
+
|
|
13
|
+
// CRT effect uniforms
|
|
14
|
+
uniform float u_curvature;
|
|
15
|
+
uniform float u_scanlineIntensity;
|
|
16
|
+
uniform float u_scanlineWidth;
|
|
17
|
+
uniform float u_shadowMask;
|
|
18
|
+
uniform float u_glowIntensity;
|
|
19
|
+
uniform float u_glowSpread;
|
|
20
|
+
uniform float u_brightness;
|
|
21
|
+
uniform float u_contrast;
|
|
22
|
+
uniform float u_saturation;
|
|
23
|
+
uniform float u_vignette;
|
|
24
|
+
uniform float u_flicker;
|
|
25
|
+
uniform float u_rgbOffset;
|
|
26
|
+
|
|
27
|
+
// New effect uniforms
|
|
28
|
+
uniform float u_staticNoise;
|
|
29
|
+
uniform float u_jitter;
|
|
30
|
+
uniform float u_horizontalSync;
|
|
31
|
+
uniform float u_glowingLine;
|
|
32
|
+
uniform float u_ambientLight;
|
|
33
|
+
uniform float u_burnIn;
|
|
34
|
+
uniform float u_overscan;
|
|
35
|
+
uniform float u_noSignal;
|
|
36
|
+
|
|
37
|
+
// NTSC fringing effect
|
|
38
|
+
uniform float u_ntscFringing;
|
|
39
|
+
|
|
40
|
+
// Color bleed - vertical inter-scanline blending (simulates CRT phosphor overlap)
|
|
41
|
+
uniform float u_colorBleed;
|
|
42
|
+
|
|
43
|
+
// Monochrome mode (0=color, 1=green, 2=amber, 3=white)
|
|
44
|
+
uniform int u_monochromeMode;
|
|
45
|
+
|
|
46
|
+
// Corner radius for rounded screen corners
|
|
47
|
+
uniform float u_cornerRadius;
|
|
48
|
+
|
|
49
|
+
// Beam position crosshair overlay (-1.0 = off, 0.0–1.0 = normalized position)
|
|
50
|
+
uniform float u_beamY;
|
|
51
|
+
uniform float u_beamX;
|
|
52
|
+
|
|
53
|
+
// Screen margin/padding for rounded corners (content is inset by this amount)
|
|
54
|
+
uniform float u_screenMargin;
|
|
55
|
+
|
|
56
|
+
varying vec2 v_texCoord;
|
|
57
|
+
|
|
58
|
+
// Constants
|
|
59
|
+
const float PI = 3.14159265359;
|
|
60
|
+
|
|
61
|
+
// ============================================
|
|
62
|
+
// Utility functions
|
|
63
|
+
// ============================================
|
|
64
|
+
|
|
65
|
+
float hash12(vec2 p) {
|
|
66
|
+
vec3 p3 = fract(vec3(p.xyx) * 0.1031);
|
|
67
|
+
p3 += dot(p3, p3.yzx + 33.33);
|
|
68
|
+
return fract((p3.x + p3.y) * p3.z);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
float rgb2grey(vec3 v) {
|
|
72
|
+
return dot(v, vec3(0.21, 0.72, 0.07));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ============================================
|
|
76
|
+
// Screen overscan/border
|
|
77
|
+
// ============================================
|
|
78
|
+
|
|
79
|
+
vec2 applyOverscan(vec2 uv) {
|
|
80
|
+
if (u_overscan < 0.001) return uv;
|
|
81
|
+
|
|
82
|
+
// Scale UV coordinates inward to create a border
|
|
83
|
+
// overscan of 1.0 = 10% border on each side (content fills 80% of screen)
|
|
84
|
+
float borderSize = u_overscan * 0.1;
|
|
85
|
+
float scale = 1.0 - (borderSize * 2.0);
|
|
86
|
+
|
|
87
|
+
// Scale from center
|
|
88
|
+
vec2 centered = uv - 0.5;
|
|
89
|
+
vec2 scaled = centered / scale;
|
|
90
|
+
return scaled + 0.5;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ============================================
|
|
94
|
+
// Screen curvature (pincushion distortion)
|
|
95
|
+
// ============================================
|
|
96
|
+
|
|
97
|
+
vec2 curveUV(vec2 uv) {
|
|
98
|
+
if (u_curvature < 0.001) return uv;
|
|
99
|
+
|
|
100
|
+
vec2 cc = uv - 0.5;
|
|
101
|
+
float dist = dot(cc, cc);
|
|
102
|
+
float distortion = dist * u_curvature * 0.5;
|
|
103
|
+
vec2 curved = uv + cc * distortion;
|
|
104
|
+
|
|
105
|
+
return curved;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ============================================
|
|
109
|
+
// Horizontal sync distortion
|
|
110
|
+
// ============================================
|
|
111
|
+
|
|
112
|
+
vec2 applyHorizontalSync(vec2 uv, float time) {
|
|
113
|
+
if (u_horizontalSync < 0.001) return uv;
|
|
114
|
+
|
|
115
|
+
float randVal = hash12(vec2(floor(time * 0.5), 0.0));
|
|
116
|
+
if (randVal > u_horizontalSync) return uv;
|
|
117
|
+
|
|
118
|
+
float distortionFreq = mix(4.0, 40.0, hash12(vec2(time * 0.1, 1.0)));
|
|
119
|
+
float distortionScale = u_horizontalSync * 0.02 * randVal;
|
|
120
|
+
float wave = sin((uv.y + time * 0.01) * distortionFreq);
|
|
121
|
+
uv.x += wave * distortionScale;
|
|
122
|
+
|
|
123
|
+
return uv;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ============================================
|
|
127
|
+
// Jitter effect
|
|
128
|
+
// ============================================
|
|
129
|
+
|
|
130
|
+
vec2 applyJitter(vec2 uv, float time) {
|
|
131
|
+
if (u_jitter < 0.001) return uv;
|
|
132
|
+
|
|
133
|
+
vec2 noiseCoord = uv * 100.0 + vec2(time * 10.0, time * 7.0);
|
|
134
|
+
vec2 offset = vec2(
|
|
135
|
+
hash12(noiseCoord) - 0.5,
|
|
136
|
+
hash12(noiseCoord + vec2(100.0, 0.0)) - 0.5
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
return uv + offset * u_jitter * 0.005;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ============================================
|
|
143
|
+
// Static noise effect (TV static style)
|
|
144
|
+
// ============================================
|
|
145
|
+
|
|
146
|
+
float staticNoise(vec2 uv, float time) {
|
|
147
|
+
if (u_staticNoise < 0.001) return 0.0;
|
|
148
|
+
|
|
149
|
+
// Blocky TV static - sized for authentic CRT look
|
|
150
|
+
vec2 blockSize = vec2(1.0, 1.0);
|
|
151
|
+
vec2 pixelCoord = floor(uv * u_textureSize / blockSize);
|
|
152
|
+
|
|
153
|
+
// Animate the noise - change every frame
|
|
154
|
+
vec2 noiseCoord = pixelCoord + vec2(
|
|
155
|
+
floor(time * 30.0) * 17.0,
|
|
156
|
+
floor(time * 30.0) * 31.0
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
float noise = hash12(noiseCoord);
|
|
160
|
+
|
|
161
|
+
// Add some scanline-like horizontal banding for authenticity
|
|
162
|
+
float scanBand = hash12(vec2(pixelCoord.y, floor(time * 15.0)));
|
|
163
|
+
noise = mix(noise, scanBand, 0.3);
|
|
164
|
+
|
|
165
|
+
// Slight vignette on the noise
|
|
166
|
+
vec2 cc = uv - 0.5;
|
|
167
|
+
float dist = length(cc);
|
|
168
|
+
float vignette = 1.0 - dist * 0.5;
|
|
169
|
+
|
|
170
|
+
return noise * u_staticNoise * vignette;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ============================================
|
|
174
|
+
// No signal TV static (full screen)
|
|
175
|
+
// ============================================
|
|
176
|
+
|
|
177
|
+
vec3 noSignalStatic(vec2 uv, float time) {
|
|
178
|
+
// Blocky TV static - sized for authentic CRT look
|
|
179
|
+
vec2 blockSize = vec2(2.0, 2.0);
|
|
180
|
+
vec2 pixelCoord = floor(uv * u_textureSize / blockSize);
|
|
181
|
+
|
|
182
|
+
// Animate for that classic TV static feel
|
|
183
|
+
float frameTime = floor(time * 50.0);
|
|
184
|
+
vec2 noiseCoord = pixelCoord + vec2(frameTime * 17.0, frameTime * 31.0);
|
|
185
|
+
|
|
186
|
+
// Base noise
|
|
187
|
+
float noise = hash12(noiseCoord);
|
|
188
|
+
|
|
189
|
+
// Horizontal banding - occasional brighter/darker scanlines
|
|
190
|
+
float bandNoise = hash12(vec2(pixelCoord.y * 0.1, frameTime * 0.5));
|
|
191
|
+
float band = smoothstep(0.4, 0.7, bandNoise);
|
|
192
|
+
noise = mix(noise * 0.7, noise * 1.2, band);
|
|
193
|
+
|
|
194
|
+
// Occasional horizontal interference lines
|
|
195
|
+
float lineNoise = hash12(vec2(frameTime, 0.0));
|
|
196
|
+
if (lineNoise > 0.95) {
|
|
197
|
+
float lineY = hash12(vec2(frameTime, 1.0));
|
|
198
|
+
float lineDist = abs(uv.y - lineY);
|
|
199
|
+
if (lineDist < 0.02) {
|
|
200
|
+
noise = mix(noise, 1.0, (0.02 - lineDist) / 0.02 * 0.5);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Slight brightness variation over time
|
|
205
|
+
float brightnessFlicker = 0.85 + hash12(vec2(frameTime * 0.1, 0.0)) * 0.3;
|
|
206
|
+
noise *= brightnessFlicker;
|
|
207
|
+
|
|
208
|
+
// Vignette
|
|
209
|
+
vec2 cc = uv - 0.5;
|
|
210
|
+
float dist = length(cc);
|
|
211
|
+
float vignette = 1.0 - dist * 0.0;
|
|
212
|
+
noise *= vignette;
|
|
213
|
+
|
|
214
|
+
// Clamp and return grayscale - reduced brightness for less dazzling effect
|
|
215
|
+
noise = clamp(noise, 0.0, 1.0) * 0.25;
|
|
216
|
+
return vec3(noise);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// ============================================
|
|
220
|
+
// Flicker effect
|
|
221
|
+
// ============================================
|
|
222
|
+
|
|
223
|
+
float flicker(float time) {
|
|
224
|
+
if (u_flicker < 0.001) return 1.0;
|
|
225
|
+
|
|
226
|
+
float noiseVal = hash12(vec2(floor(time * 15.0), 0.0));
|
|
227
|
+
return 1.0 + (noiseVal - 0.5) * u_flicker * 0.15;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ============================================
|
|
231
|
+
// Glowing line effect (scanning beam)
|
|
232
|
+
// ============================================
|
|
233
|
+
|
|
234
|
+
float glowingLine(vec2 uv, float time) {
|
|
235
|
+
if (u_glowingLine < 0.001) return 0.0;
|
|
236
|
+
|
|
237
|
+
float beamPos = fract(time * 0.05);
|
|
238
|
+
float dist = abs(uv.y - beamPos);
|
|
239
|
+
float glow = smoothstep(0.1, 0.0, dist);
|
|
240
|
+
|
|
241
|
+
return glow * u_glowingLine * 0.3;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// ============================================
|
|
245
|
+
// Ambient light effect
|
|
246
|
+
// ============================================
|
|
247
|
+
|
|
248
|
+
vec3 applyAmbientLight(vec3 color, vec2 uv) {
|
|
249
|
+
if (u_ambientLight < 0.001) return color;
|
|
250
|
+
|
|
251
|
+
vec2 cc = uv - 0.5;
|
|
252
|
+
float dist = length(cc);
|
|
253
|
+
float ambient = (1.0 - dist) * (1.0 - dist);
|
|
254
|
+
|
|
255
|
+
return color + vec3(u_ambientLight * ambient * 0.15);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// ============================================
|
|
259
|
+
// Scanline effect
|
|
260
|
+
// ============================================
|
|
261
|
+
|
|
262
|
+
float scanlines(vec2 uv) {
|
|
263
|
+
if (u_scanlineIntensity < 0.001) return 1.0;
|
|
264
|
+
|
|
265
|
+
float scanline = sin(uv.y * u_textureSize.y * PI) * 0.5 + 0.5;
|
|
266
|
+
scanline = pow(scanline, u_scanlineWidth * 2.0 + 0.5);
|
|
267
|
+
return mix(1.0, scanline, u_scanlineIntensity);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// ============================================
|
|
271
|
+
// Shadow mask
|
|
272
|
+
// ============================================
|
|
273
|
+
|
|
274
|
+
vec3 shadowMask(vec2 uv) {
|
|
275
|
+
if (u_shadowMask < 0.001) return vec3(1.0);
|
|
276
|
+
|
|
277
|
+
vec2 pos = uv * u_resolution;
|
|
278
|
+
int px = int(mod(pos.x, 3.0));
|
|
279
|
+
|
|
280
|
+
vec3 mask;
|
|
281
|
+
if (px == 0) {
|
|
282
|
+
mask = vec3(1.0, 0.7, 0.7);
|
|
283
|
+
} else if (px == 1) {
|
|
284
|
+
mask = vec3(0.7, 1.0, 0.7);
|
|
285
|
+
} else {
|
|
286
|
+
mask = vec3(0.7, 0.7, 1.0);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return mix(vec3(1.0), mask, u_shadowMask);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// ============================================
|
|
293
|
+
// Vignette effect
|
|
294
|
+
// ============================================
|
|
295
|
+
|
|
296
|
+
float vignette(vec2 uv) {
|
|
297
|
+
if (u_vignette < 0.001) return 1.0;
|
|
298
|
+
|
|
299
|
+
vec2 center = uv - 0.5;
|
|
300
|
+
float dist = length(center);
|
|
301
|
+
float vig = 1.0 - dist * dist * u_vignette * 2.0;
|
|
302
|
+
return clamp(vig, 0.0, 1.0);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// ============================================
|
|
306
|
+
// Phosphor glow / bloom effect
|
|
307
|
+
// ============================================
|
|
308
|
+
|
|
309
|
+
vec3 glow(sampler2D tex, vec2 uv) {
|
|
310
|
+
if (u_glowIntensity < 0.001) return vec3(0.0);
|
|
311
|
+
|
|
312
|
+
vec3 bloom = vec3(0.0);
|
|
313
|
+
float spread = u_glowSpread * 0.01;
|
|
314
|
+
|
|
315
|
+
for (int x = -1; x <= 1; x++) {
|
|
316
|
+
for (int y = -1; y <= 1; y++) {
|
|
317
|
+
vec2 offset = vec2(float(x), float(y)) * spread;
|
|
318
|
+
bloom += texture2D(tex, uv + offset).rgb;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
bloom /= 9.0;
|
|
322
|
+
|
|
323
|
+
return bloom * u_glowIntensity;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// ============================================
|
|
327
|
+
// RGB chromatic aberration
|
|
328
|
+
// ============================================
|
|
329
|
+
|
|
330
|
+
vec3 rgbShift(sampler2D tex, vec2 uv) {
|
|
331
|
+
if (u_rgbOffset < 0.001) return texture2D(tex, uv).rgb;
|
|
332
|
+
|
|
333
|
+
vec2 dir = uv - 0.5;
|
|
334
|
+
float offset = u_rgbOffset * 0.003;
|
|
335
|
+
|
|
336
|
+
vec2 rOffset = dir * offset;
|
|
337
|
+
vec2 bOffset = -dir * offset;
|
|
338
|
+
|
|
339
|
+
float r = texture2D(tex, uv + rOffset).r;
|
|
340
|
+
float g = texture2D(tex, uv).g;
|
|
341
|
+
float b = texture2D(tex, uv + bOffset).b;
|
|
342
|
+
|
|
343
|
+
return vec3(r, g, b);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// ============================================
|
|
347
|
+
// Color Bleed (vertical inter-scanline blending)
|
|
348
|
+
// Simulates CRT phosphor spot overlap where
|
|
349
|
+
// adjacent scanlines bleed into each other
|
|
350
|
+
// ============================================
|
|
351
|
+
|
|
352
|
+
vec3 colorBleed(sampler2D tex, vec2 uv, vec3 baseColor) {
|
|
353
|
+
if (u_colorBleed < 0.001) return baseColor;
|
|
354
|
+
|
|
355
|
+
vec2 texelSize = 1.0 / u_textureSize;
|
|
356
|
+
|
|
357
|
+
// 5-tap vertical kernel: sample 2 rows above and below
|
|
358
|
+
// Weights 1-1-2-1-1 (sum 6) chosen to perfectly cancel the common
|
|
359
|
+
// Apple II HIRES pattern where artifact colors alternate every 2 rows
|
|
360
|
+
// (each scanline is doubled, giving a BBVV period-4 pattern)
|
|
361
|
+
vec3 up2 = texture2D(tex, uv + vec2(0.0, -2.0 * texelSize.y)).rgb;
|
|
362
|
+
vec3 up1 = texture2D(tex, uv + vec2(0.0, -1.0 * texelSize.y)).rgb;
|
|
363
|
+
vec3 dn1 = texture2D(tex, uv + vec2(0.0, 1.0 * texelSize.y)).rgb;
|
|
364
|
+
vec3 dn2 = texture2D(tex, uv + vec2(0.0, 2.0 * texelSize.y)).rgb;
|
|
365
|
+
|
|
366
|
+
vec3 blended = (up2 + up1 + baseColor * 2.0 + dn1 + dn2) / 6.0;
|
|
367
|
+
|
|
368
|
+
return mix(baseColor, blended, u_colorBleed);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// ============================================
|
|
372
|
+
// NTSC Color Fringing
|
|
373
|
+
// Simulates the limited chroma bandwidth of NTSC
|
|
374
|
+
// causing color "ringing" at sharp edges
|
|
375
|
+
// ============================================
|
|
376
|
+
|
|
377
|
+
vec3 ntscFringing(sampler2D tex, vec2 uv, vec3 baseColor) {
|
|
378
|
+
if (u_ntscFringing < 0.001) return baseColor;
|
|
379
|
+
|
|
380
|
+
// Pixel size for sampling neighbors
|
|
381
|
+
vec2 pixelSize = 1.0 / u_textureSize;
|
|
382
|
+
|
|
383
|
+
// Sample neighboring pixels horizontally (NTSC fringing is horizontal)
|
|
384
|
+
vec3 left2 = texture2D(tex, uv + vec2(-2.0 * pixelSize.x, 0.0)).rgb;
|
|
385
|
+
vec3 left1 = texture2D(tex, uv + vec2(-1.0 * pixelSize.x, 0.0)).rgb;
|
|
386
|
+
vec3 right1 = texture2D(tex, uv + vec2(1.0 * pixelSize.x, 0.0)).rgb;
|
|
387
|
+
vec3 right2 = texture2D(tex, uv + vec2(2.0 * pixelSize.x, 0.0)).rgb;
|
|
388
|
+
|
|
389
|
+
// Calculate brightness (luma) for edge detection
|
|
390
|
+
float lumaCenter = rgb2grey(baseColor);
|
|
391
|
+
float lumaLeft1 = rgb2grey(left1);
|
|
392
|
+
float lumaLeft2 = rgb2grey(left2);
|
|
393
|
+
float lumaRight1 = rgb2grey(right1);
|
|
394
|
+
float lumaRight2 = rgb2grey(right2);
|
|
395
|
+
|
|
396
|
+
// Detect edges - looking for significant brightness transitions
|
|
397
|
+
float leftAvg = (lumaLeft1 + lumaLeft2) * 0.5;
|
|
398
|
+
float rightAvg = (lumaRight1 + lumaRight2) * 0.5;
|
|
399
|
+
|
|
400
|
+
// Edge strength: how much brighter is one side vs the other
|
|
401
|
+
float leftEdge = max(0.0, rightAvg - leftAvg); // Bright on right = left edge of bright area
|
|
402
|
+
float rightEdge = max(0.0, leftAvg - rightAvg); // Bright on left = right edge of bright area
|
|
403
|
+
|
|
404
|
+
// Only apply fringing where there's a significant edge
|
|
405
|
+
float edgeThreshold = 0.15;
|
|
406
|
+
leftEdge = smoothstep(edgeThreshold, edgeThreshold + 0.2, leftEdge);
|
|
407
|
+
rightEdge = smoothstep(edgeThreshold, edgeThreshold + 0.2, rightEdge);
|
|
408
|
+
|
|
409
|
+
// NTSC fringe colors (magenta on left edges, cyan on right edges)
|
|
410
|
+
vec3 magentaFringe = vec3(0.84, 0.26, 1.0); // Purple/Magenta
|
|
411
|
+
vec3 cyanFringe = vec3(0.42, 0.90, 0.72); // Aqua/Cyan
|
|
412
|
+
|
|
413
|
+
// Apply fringing with smooth blending
|
|
414
|
+
// Fringe is stronger on darker pixels near bright edges
|
|
415
|
+
float darkness = 1.0 - lumaCenter;
|
|
416
|
+
float fringeStrength = u_ntscFringing * darkness * 0.7;
|
|
417
|
+
|
|
418
|
+
vec3 result = baseColor;
|
|
419
|
+
|
|
420
|
+
// Left edge fringing (magenta)
|
|
421
|
+
if (leftEdge > 0.0) {
|
|
422
|
+
result = mix(result, magentaFringe, leftEdge * fringeStrength);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Right edge fringing (cyan)
|
|
426
|
+
if (rightEdge > 0.0) {
|
|
427
|
+
result = mix(result, cyanFringe, rightEdge * fringeStrength);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Add subtle horizontal color blur to simulate chroma bandwidth limiting
|
|
431
|
+
// This makes the overall color response smoother
|
|
432
|
+
vec3 blurredChroma = (left1 + baseColor + right1) / 3.0;
|
|
433
|
+
float chromaBlur = u_ntscFringing * 0.15;
|
|
434
|
+
result = mix(result, blurredChroma, chromaBlur);
|
|
435
|
+
|
|
436
|
+
return result;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// ============================================
|
|
440
|
+
// Color adjustment
|
|
441
|
+
// ============================================
|
|
442
|
+
|
|
443
|
+
vec3 adjustColor(vec3 color) {
|
|
444
|
+
color *= u_brightness;
|
|
445
|
+
color = (color - 0.5) * u_contrast + 0.5;
|
|
446
|
+
float gray = rgb2grey(color);
|
|
447
|
+
color = mix(vec3(gray), color, u_saturation);
|
|
448
|
+
return color;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// ============================================
|
|
452
|
+
// Monochrome mode
|
|
453
|
+
// ============================================
|
|
454
|
+
|
|
455
|
+
vec3 applyMonochrome(vec3 color) {
|
|
456
|
+
if (u_monochromeMode == 0) return color; // Color mode - no change
|
|
457
|
+
|
|
458
|
+
// Convert to grayscale using luminance
|
|
459
|
+
float gray = rgb2grey(color);
|
|
460
|
+
|
|
461
|
+
// Apply tint based on monochrome mode
|
|
462
|
+
if (u_monochromeMode == 1) {
|
|
463
|
+
// Green phosphor (classic Apple II monitor)
|
|
464
|
+
// P1 phosphor green: slightly blue-green tint
|
|
465
|
+
return vec3(gray * 0.2, gray * 1.0, gray * 0.2);
|
|
466
|
+
} else if (u_monochromeMode == 2) {
|
|
467
|
+
// Amber phosphor (common on IBM PCs)
|
|
468
|
+
// Warm orange-yellow tint
|
|
469
|
+
return vec3(gray * 1.0, gray * 0.75, gray * 0.2);
|
|
470
|
+
} else if (u_monochromeMode == 3) {
|
|
471
|
+
// White phosphor (paper white)
|
|
472
|
+
// Slight warm tint for authenticity
|
|
473
|
+
return vec3(gray * 1.0, gray * 1.0, gray * 0.9);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
return color;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// ============================================
|
|
480
|
+
// Edge effects
|
|
481
|
+
// ============================================
|
|
482
|
+
|
|
483
|
+
float edgeFade(vec2 uv) {
|
|
484
|
+
vec2 edge = smoothstep(0.0, 0.005, uv) * smoothstep(0.0, 0.005, 1.0 - uv);
|
|
485
|
+
return mix(0.85, 1.0, edge.x * edge.y);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
float smoothEdge(vec2 uv) {
|
|
489
|
+
if (u_curvature < 0.001 && u_cornerRadius < 0.001) return 1.0;
|
|
490
|
+
|
|
491
|
+
vec2 centered = uv - 0.5;
|
|
492
|
+
// Use explicit corner radius if set, otherwise derive from curvature
|
|
493
|
+
float cornerRadius = u_cornerRadius > 0.001 ? u_cornerRadius : u_curvature * 0.03;
|
|
494
|
+
vec2 cornerDist = abs(centered) - (0.5 - cornerRadius);
|
|
495
|
+
cornerDist = max(cornerDist, 0.0);
|
|
496
|
+
float corner = length(cornerDist) / cornerRadius;
|
|
497
|
+
|
|
498
|
+
return 1.0 - smoothstep(0.9, 1.0, corner);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Rounded rectangle SDF for clean corner masking
|
|
502
|
+
float roundedRectAlpha(vec2 uv, float radius) {
|
|
503
|
+
if (radius < 0.001) return 1.0;
|
|
504
|
+
|
|
505
|
+
vec2 centered = abs(uv - 0.5);
|
|
506
|
+
vec2 cornerDist = centered - (0.5 - radius);
|
|
507
|
+
|
|
508
|
+
// Inside the rectangle (not in corner region)
|
|
509
|
+
if (cornerDist.x < 0.0 || cornerDist.y < 0.0) {
|
|
510
|
+
return 1.0;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// In corner region - use distance from corner arc
|
|
514
|
+
float dist = length(cornerDist);
|
|
515
|
+
// Smooth anti-aliased edge
|
|
516
|
+
return 1.0 - smoothstep(radius - 0.005, radius + 0.005, dist);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// Apply screen margin - scales content inward so corners don't clip it
|
|
520
|
+
vec2 applyScreenMargin(vec2 uv) {
|
|
521
|
+
if (u_screenMargin < 0.001) return uv;
|
|
522
|
+
|
|
523
|
+
// Scale UV from center to create margin
|
|
524
|
+
vec2 centered = uv - 0.5;
|
|
525
|
+
float scale = 1.0 / (1.0 - u_screenMargin * 2.0);
|
|
526
|
+
return centered * scale + 0.5;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// ============================================
|
|
530
|
+
// Beam position crosshair overlay
|
|
531
|
+
// ============================================
|
|
532
|
+
|
|
533
|
+
vec3 beamOverlay(vec2 uv) {
|
|
534
|
+
if (u_beamY < 0.0 && u_beamX < 0.0) return vec3(0.0);
|
|
535
|
+
|
|
536
|
+
// Line thickness in UV space (~1.5 pixels)
|
|
537
|
+
float lineW = 1.5 / u_textureSize.x;
|
|
538
|
+
float lineH = 1.5 / u_textureSize.y;
|
|
539
|
+
|
|
540
|
+
vec3 lineColor = vec3(1.0, 0.0, 0.0); // Red
|
|
541
|
+
float intensity = 0.0;
|
|
542
|
+
|
|
543
|
+
// Horizontal line at beamY
|
|
544
|
+
if (u_beamY >= 0.0 && u_beamY <= 1.0) {
|
|
545
|
+
float dy = abs(uv.y - u_beamY);
|
|
546
|
+
intensity += smoothstep(lineH, 0.0, dy) * 0.6;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// Vertical line at beamX
|
|
550
|
+
if (u_beamX >= 0.0 && u_beamX <= 1.0) {
|
|
551
|
+
float dx = abs(uv.x - u_beamX);
|
|
552
|
+
intensity += smoothstep(lineW, 0.0, dx) * 0.6;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
return lineColor * min(intensity, 1.0);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// ============================================
|
|
559
|
+
// Main fragment shader
|
|
560
|
+
// ============================================
|
|
561
|
+
|
|
562
|
+
void main() {
|
|
563
|
+
vec2 uv = v_texCoord;
|
|
564
|
+
|
|
565
|
+
// Stable screen boundary from undistorted coordinates.
|
|
566
|
+
// The physical CRT mask doesn't wobble — only the beam does.
|
|
567
|
+
// All clipping and alpha use this so nothing renders outside the edge.
|
|
568
|
+
vec2 stableCurvedUV = curveUV(uv);
|
|
569
|
+
|
|
570
|
+
float cornerAlpha = roundedRectAlpha(stableCurvedUV, u_cornerRadius);
|
|
571
|
+
if (cornerAlpha < 0.001) {
|
|
572
|
+
gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
float edgeFactor = smoothEdge(stableCurvedUV);
|
|
577
|
+
if (edgeFactor < 0.001) {
|
|
578
|
+
gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
if (stableCurvedUV.x < 0.0 || stableCurvedUV.x > 1.0 || stableCurvedUV.y < 0.0 || stableCurvedUV.y > 1.0) {
|
|
583
|
+
gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// Apply signal distortions — the beam wobbles, the mask does not
|
|
588
|
+
vec2 distortedUV = applyHorizontalSync(uv, u_time);
|
|
589
|
+
distortedUV = applyJitter(distortedUV, u_time);
|
|
590
|
+
vec2 curvedUV = curveUV(distortedUV);
|
|
591
|
+
|
|
592
|
+
// Content coordinates use the distorted beam position
|
|
593
|
+
vec2 contentUV = applyOverscan(curvedUV);
|
|
594
|
+
contentUV = applyScreenMargin(contentUV);
|
|
595
|
+
|
|
596
|
+
// Dark bezel color for areas outside content
|
|
597
|
+
vec3 darkBezelColor = vec3(0.0); // Black
|
|
598
|
+
|
|
599
|
+
// Check if we're in the margin area (outside content but inside screen)
|
|
600
|
+
bool inMargin = contentUV.x < 0.0 || contentUV.x > 1.0 || contentUV.y < 0.0 || contentUV.y > 1.0;
|
|
601
|
+
|
|
602
|
+
// No signal mode - show TV static instead of emulator content
|
|
603
|
+
if (u_noSignal > 0.5) {
|
|
604
|
+
vec3 staticColor = noSignalStatic(curvedUV, u_time);
|
|
605
|
+
|
|
606
|
+
// Apply scanlines to static for authentic look
|
|
607
|
+
staticColor *= scanlines(curvedUV);
|
|
608
|
+
|
|
609
|
+
// Apply vignette
|
|
610
|
+
staticColor *= vignette(curvedUV);
|
|
611
|
+
|
|
612
|
+
// Apply edge fade for curved screens
|
|
613
|
+
if (u_curvature > 0.001) {
|
|
614
|
+
staticColor *= edgeFade(curvedUV);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
float staticAlpha = cornerAlpha * edgeFactor;
|
|
618
|
+
gl_FragColor = vec4(staticColor, staticAlpha);
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// Get base color - dark bezel color for margin area, texture sample for content
|
|
623
|
+
vec3 color;
|
|
624
|
+
if (inMargin) {
|
|
625
|
+
color = darkBezelColor;
|
|
626
|
+
} else {
|
|
627
|
+
// Get base color with RGB shift
|
|
628
|
+
color = rgbShift(u_texture, contentUV);
|
|
629
|
+
|
|
630
|
+
// Apply vertical color bleed (CRT inter-scanline blending)
|
|
631
|
+
color = colorBleed(u_texture, contentUV, color);
|
|
632
|
+
|
|
633
|
+
// Apply NTSC color fringing only in colour mode
|
|
634
|
+
if (u_monochromeMode == 0) {
|
|
635
|
+
color = ntscFringing(u_texture, contentUV, color);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// Apply texture-based effects only for content area
|
|
640
|
+
if (!inMargin) {
|
|
641
|
+
// Blend text selection overlay (before burn-in and glow so CRT effects apply on top)
|
|
642
|
+
vec4 sel = texture2D(u_selectionTexture, contentUV);
|
|
643
|
+
if (sel.a > 0.0) {
|
|
644
|
+
color = mix(color, sel.rgb, sel.a);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// Apply burn-in from accumulation buffer
|
|
648
|
+
if (u_burnIn > 0.001) {
|
|
649
|
+
// Burn-in texture is stored in non-flipped coords, flip Y to match main texture
|
|
650
|
+
vec2 burnInCoord = vec2(contentUV.x, 1.0 - contentUV.y);
|
|
651
|
+
vec3 burnInColor = texture2D(u_burnInTexture, burnInCoord).rgb;
|
|
652
|
+
color = max(color, burnInColor * u_burnIn);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// Add phosphor glow
|
|
656
|
+
color += glow(u_texture, contentUV);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// Apply scanlines (use curvedUV for consistent scanlines across margin)
|
|
660
|
+
color *= scanlines(curvedUV);
|
|
661
|
+
|
|
662
|
+
// Apply shadow mask
|
|
663
|
+
color *= shadowMask(curvedUV);
|
|
664
|
+
|
|
665
|
+
// Apply color adjustments (brightness, contrast, saturation)
|
|
666
|
+
color = adjustColor(color);
|
|
667
|
+
|
|
668
|
+
// Apply monochrome mode (after color adjustments, before vignette)
|
|
669
|
+
color = applyMonochrome(color);
|
|
670
|
+
|
|
671
|
+
// Apply vignette
|
|
672
|
+
color *= vignette(curvedUV);
|
|
673
|
+
|
|
674
|
+
// Apply edge fade for curved screens (uses stable coords — physical screen property)
|
|
675
|
+
if (u_curvature > 0.001) {
|
|
676
|
+
color *= edgeFade(stableCurvedUV);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// Apply flicker
|
|
680
|
+
color *= flicker(u_time);
|
|
681
|
+
|
|
682
|
+
// Add glowing line
|
|
683
|
+
color += vec3(glowingLine(curvedUV, u_time));
|
|
684
|
+
|
|
685
|
+
// Add static noise
|
|
686
|
+
color += vec3(staticNoise(curvedUV, u_time));
|
|
687
|
+
|
|
688
|
+
// Apply ambient light
|
|
689
|
+
color = applyAmbientLight(color, curvedUV);
|
|
690
|
+
|
|
691
|
+
// Beam position crosshair (opaque overlay, stable UV — only curve applied)
|
|
692
|
+
{
|
|
693
|
+
vec2 beamUV = applyOverscan(stableCurvedUV);
|
|
694
|
+
beamUV = applyScreenMargin(beamUV);
|
|
695
|
+
vec3 beam = beamOverlay(beamUV);
|
|
696
|
+
color = mix(color, vec3(1.0, 0.0, 0.0), beam.r);
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// Clamp final color
|
|
700
|
+
color = clamp(color, 0.0, 1.0);
|
|
701
|
+
|
|
702
|
+
// Alpha combines corner rounding and curvature edge fade
|
|
703
|
+
float alpha = cornerAlpha * edgeFactor;
|
|
704
|
+
|
|
705
|
+
gl_FragColor = vec4(color, alpha);
|
|
706
|
+
}
|