Some plumbing for upcoming fake compute shaders

This commit is contained in:
Luke Hubmayer-Werner 2024-07-10 00:35:29 +09:30
parent b07ed09513
commit 80cbfa7ab8
4 changed files with 163 additions and 10 deletions

View File

@ -120,6 +120,11 @@ var EVENT_MAP: Dictionary
var NOTE_DURATIONS: PoolByteArray # This might need to be made untyped if a future addition uses PoolIntArray instead
var REFERENCE_NOTE: int
const LOGGING_LEVEL_INFO: bool = false
func print_info(s: String) -> void:
if LOGGING_LEVEL_INFO:
print(s)
# These would be static methods if NOTE_DURATIONS and EVENT_MAP could be static
func translate_instruction(buffer: StreamPeer) -> Array:
var instruction := buffer.get_u8()
@ -189,7 +194,7 @@ func unroll_track(buffer: StreamPeerBuffer, bgm_start_pos: int, track_start_pos:
var break_pos := loop_break_positions[loop_level]
var jump_pos := loop_break_targets[loop_level]
if jump_pos - pos != 1: # This happens once on track 68:06 FFV SNES BGM
print('LOOP_END at 0x%06X would break instead from 0x%06X to 0x%06X (%d past end)' % [pos, break_pos, jump_pos, jump_pos-pos])
print_debug('LOOP_END at 0x%06X would break instead from 0x%06X to 0x%06X (%d past end)' % [pos, break_pos, jump_pos, jump_pos-pos])
buffer.seek(jump_pos)
var loop_events := events.slice(rom_address_to_event_index[loop_positions[loop_level]], -1)
var partial_loop_events := events.slice(rom_address_to_event_index[loop_positions[loop_level]], rom_address_to_event_index[break_pos]-1)
@ -211,7 +216,7 @@ func unroll_track(buffer: StreamPeerBuffer, bgm_start_pos: int, track_start_pos:
loop_level -= 1
else:
# Infinite loop, convert it to a GOTO and return
print('ended with infinite loop')
print_info('ended with infinite loop')
events.append([EventType.GOTO, rom_address_to_event_index[loop_break_targets[loop_level]]])
loop_level -= 1
break
@ -225,10 +230,10 @@ func unroll_track(buffer: StreamPeerBuffer, bgm_start_pos: int, track_start_pos:
loop_break_positions[loop_level] = pos
loop_break_targets[loop_level] = target_pos
if on_loop != loop_counts[loop_level]:
print('LOOP_BREAK found in track %s at 0x%06X:0x%06X:0x%06X: 0x%06X to 0x%06X on loop %d of %d' % [bgm_id, bgm_start_pos, track_start_pos, bgm_end_pos, pos, target_pos, on_loop, loop_counts[loop_level]])
print_info('LOOP_BREAK found in track %s at 0x%06X:0x%06X:0x%06X: 0x%06X to 0x%06X on loop %d of %d' % [bgm_id, bgm_start_pos, track_start_pos, bgm_end_pos, pos, target_pos, on_loop, loop_counts[loop_level]])
loop_counts[loop_level] = on_loop
else:
print('LOOP_BREAK found in track %s at 0x%06X:0x%06X:0x%06X: 0x%06X to 0x%06X on last loop' % [bgm_id, bgm_start_pos, track_start_pos, bgm_end_pos, pos, target_pos])
print_info('LOOP_BREAK found in track %s at 0x%06X:0x%06X:0x%06X: 0x%06X to 0x%06X on last loop' % [bgm_id, bgm_start_pos, track_start_pos, bgm_end_pos, pos, target_pos])
if target_pos in rom_address_to_event_index:
print_debug('LOOP_BREAK is a past address, this is either malformed or an infinite loop')
break
@ -242,7 +247,7 @@ func unroll_track(buffer: StreamPeerBuffer, bgm_start_pos: int, track_start_pos:
target_pos += 0x010000
var event_num = rom_address_to_event_index.get(target_pos, -1)
if event_num >= 0:
print('Infinite GOTO found in track %s at 0x%06X:0x%06X:0x%06X: 0x%06X to 0x%06X (event number %d)' % [bgm_id, bgm_start_pos, track_start_pos, bgm_end_pos, pos, target_pos, event_num])
print_info('Infinite GOTO found in track %s at 0x%06X:0x%06X:0x%06X: 0x%06X to 0x%06X (event number %d)' % [bgm_id, bgm_start_pos, track_start_pos, bgm_end_pos, pos, target_pos, event_num])
events.append([EventType.GOTO, rom_address_to_event_index[target_pos]])
break
else:
@ -253,10 +258,10 @@ func unroll_track(buffer: StreamPeerBuffer, bgm_start_pos: int, track_start_pos:
event_idx += 1
# DEBUG: Report if track doesn't end all the contained loops
if loop_level >= 0:
print('track %s ended on loop_level %d (0x%06X)' % [bgm_id, loop_level, loop_positions[loop_level]])
print_info('track %s ended on loop_level %d (0x%06X)' % [bgm_id, loop_level, loop_positions[loop_level]])
# DEBUG: Report total note duration and repeat note duration
if events:
print('%d events' % len(events))
print_info('%d events' % len(events))
var total_note_dur := 0
var total_notes := 0
var total_rests := 0
@ -272,7 +277,7 @@ func unroll_track(buffer: StreamPeerBuffer, bgm_start_pos: int, track_start_pos:
for e in events.slice(events[-1][1], -1):
if e[0] == EventType.NOTE:
repeat_note_dur += e[2]
print('track %s has duration %d and repeat duration %d (intro %d) - %d notes %d rests' % [bgm_id, total_note_dur, repeat_note_dur, total_note_dur-repeat_note_dur, total_notes, total_rests])
print_info('track %s has duration %d and repeat duration %d (intro %d) - %d notes %d rests' % [bgm_id, total_note_dur, repeat_note_dur, total_note_dur-repeat_note_dur, total_notes, total_rests])
else:
print('track %s has duration %d - %d notes %d rests' % [bgm_id, total_note_dur, total_notes, total_rests])
print_info('track %s has duration %d - %d notes %d rests' % [bgm_id, total_note_dur, total_notes, total_rests])
return events

View File

@ -0,0 +1,57 @@
shader_type canvas_item;
render_mode blend_premul_alpha;
const float TEX_SIZE = 4096.0;
const float UV_QUANTIZE = TEX_SIZE;
// I feel like these magic numbers are a bit more intuitive in hex
const float x00FF = float(0x00FF); // 255.0
const float x0100 = float(0x0100); // 256.0
const float x7FFF = float(0x7FFF); // 32767.0
const float x8000 = float(0x8000); // 32768.0
const float xFF00 = float(0xFF00); // 65280.0
const float xFFFF = float(0xFFFF); // 65535.0
const float x10000 = float(0x10000); // 65536.0
const vec2 INT16_DOT_BE = vec2(xFF00, x00FF);
const vec2 INT16_DOT_LE = vec2(x00FF, xFF00);
uniform sampler2D tex : hint_normal;
float unpack_int16(vec2 int16) {
// Convert packed 2byte integer, sampled as two [0.0, 1.0] range floats,
// to the original int value [-32768, 32767] or [0, 65535] but in float32
float unsigned = dot(int16, INT16_DOT_LE);
return unsigned - (unsigned < x7FFF ? 0.0 : x10000);
}
float rescale_int16(float int16) {
// Rescale from [-32768, 32767] to [-1.0, 1.0)
return int16 / x8000;
}
vec2 pack_float_to_int16(float value) {
// Convert a float in range [-1.0, 1.0) to a signed 2byte integer [-32768, 32767] packed into two [0.0, 1.0] floats
float scaled = value * x8000;
float unsigned = scaled + (scaled < 0.0 ? x10000 : 0.0);
float unsigned_div_256 = unsigned / x0100;
float MSB = trunc(unsigned_div_256) / x00FF;
float LSB = fract(unsigned_div_256) * x0100 / x00FF;
return vec2(LSB, MSB);
}
vec4 test_writeback(vec2 uv) {
// Test importing and exporting the samples,
// and exporting a value derived from the UV
vec4 output;
float sample_1 = rescale_int16(unpack_int16(texture(tex, uv).xw));
float sample_2 = rescale_int16(dot(trunc(uv*TEX_SIZE), vec2(1.0, TEX_SIZE)));
output.xy = pack_float_to_int16(sample_1);
output.zw = pack_float_to_int16(sample_2);
return output;
}
void fragment() {
// GLES2
vec2 uv = vec2(UV.x, 1.0-UV.y);
uv = (trunc(uv*UV_QUANTIZE)+0.5)/UV_QUANTIZE;
COLOR.xyzw = test_writeback(uv);
}

70
test/audio_renderer.gd Normal file
View File

@ -0,0 +1,70 @@
extends Control
var viewport: Viewport
var render_queue: Array # of PoolByteArrays
var result_queue: Array # of PoolByteArrays
var current_image: Image
var current_tex: ImageTexture # Needed to prevent GC before draw
var waiting_for_viewport: bool
var done_first_draw: bool
func _ready() -> void:
self.viewport = get_parent()
self.render_queue = []
self.result_queue = []
self.waiting_for_viewport = false
self.done_first_draw = false
self.current_image = Image.new()
self.current_tex = ImageTexture.new()
func _process(_delta) -> void:
update()
func _draw() -> void:
# Seems like the first one always fails
if not self.done_first_draw:
self.done_first_draw = true
return
if self.waiting_for_viewport:
# Another node later in the draw sequence can call this within the same frame,
# otherwise, this picks it up the following frame
get_result()
if not self.render_queue:
return
# Draw the next ImageTexture
var data: PoolByteArray = self.render_queue.pop_front()
print(data.subarray(0, 15))
self.current_image.create_from_data(4096, 4096, false, Image.FORMAT_LA8, data)
self.current_tex.create_from_image(self.current_image, Texture.FLAG_FILTER)
self.material.set_shader_param('tex', self.current_tex)
draw_texture(self.current_tex, Vector2.ZERO)
self.waiting_for_viewport = true # Grab the result next draw
func get_result() -> void:
var result_texture := self.viewport.get_texture()
var result_image := result_texture.get_data()
var result_bytes := result_image.get_data()
# Debugging: compare a sequence of all the possible 16bit integers
print_debug('result_image format is %d and has size'%result_image.get_format(), result_image.get_size(), result_bytes.subarray(0, 11))
test_readback(result_bytes)
self.result_queue.append(result_bytes)
self.waiting_for_viewport = false
func test_readback(result_bytes: PoolByteArray):
# Debugging: compare a sequence of all the possible 16bit integers
var buff := StreamPeerBuffer.new()
buff.set_data_array(result_bytes)
var tex_readback = 0
var uv_readback = 0
for i in 0x1000:
tex_readback = buff.get_u16()
uv_readback = buff.get_u16()
if tex_readback != i:
print('tex readback %d (0x%04x) was instead %d (0x%04x)'%[i, i, tex_readback, tex_readback])
if uv_readback != i:
print('uv readback %d (0x%04x) was instead %d (0x%04x)'%[i, i, uv_readback, uv_readback])

View File

@ -1,11 +1,31 @@
[gd_scene load_steps=3 format=2]
[gd_scene load_steps=6 format=2]
[ext_resource path="res://test/audio_system.gd" type="Script" id=1]
[ext_resource path="res://theme/menu_theme.tres" type="Theme" id=2]
[ext_resource path="res://test/audio_renderer.gd" type="Script" id=3]
[ext_resource path="res://shaders/audio_renderer.gdshader" type="Shader" id=4]
[sub_resource type="ShaderMaterial" id=2]
shader = ExtResource( 4 )
[node name="audio_system" type="Node2D"]
script = ExtResource( 1 )
[node name="viewport_audio_renderer" type="Viewport" parent="."]
size = Vector2( 4096, 4096 )
size_override_stretch = true
transparent_bg = true
handle_input_locally = false
hdr = false
render_target_update_mode = 3
[node name="audio_renderer" type="Control" parent="viewport_audio_renderer"]
unique_name_in_owner = true
material = SubResource( 2 )
margin_right = 40.0
margin_bottom = 40.0
script = ExtResource( 3 )
[node name="inst_buttons" type="ReferenceRect" parent="."]
margin_right = 348.0
margin_bottom = 118.0
@ -42,4 +62,5 @@ margin_top = 192.0
margin_right = 315.0
margin_bottom = 216.0
theme = ExtResource( 2 )
pressed = true
text = "HACK: Loop Extension"