Some plumbing for upcoming fake compute shaders
This commit is contained in:
parent
b07ed09513
commit
80cbfa7ab8
|
@ -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 NOTE_DURATIONS: PoolByteArray # This might need to be made untyped if a future addition uses PoolIntArray instead
|
||||||
var REFERENCE_NOTE: int
|
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
|
# These would be static methods if NOTE_DURATIONS and EVENT_MAP could be static
|
||||||
func translate_instruction(buffer: StreamPeer) -> Array:
|
func translate_instruction(buffer: StreamPeer) -> Array:
|
||||||
var instruction := buffer.get_u8()
|
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 break_pos := loop_break_positions[loop_level]
|
||||||
var jump_pos := loop_break_targets[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
|
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)
|
buffer.seek(jump_pos)
|
||||||
var loop_events := events.slice(rom_address_to_event_index[loop_positions[loop_level]], -1)
|
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)
|
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
|
loop_level -= 1
|
||||||
else:
|
else:
|
||||||
# Infinite loop, convert it to a GOTO and return
|
# 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]]])
|
events.append([EventType.GOTO, rom_address_to_event_index[loop_break_targets[loop_level]]])
|
||||||
loop_level -= 1
|
loop_level -= 1
|
||||||
break
|
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_positions[loop_level] = pos
|
||||||
loop_break_targets[loop_level] = target_pos
|
loop_break_targets[loop_level] = target_pos
|
||||||
if on_loop != loop_counts[loop_level]:
|
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
|
loop_counts[loop_level] = on_loop
|
||||||
else:
|
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:
|
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')
|
print_debug('LOOP_BREAK is a past address, this is either malformed or an infinite loop')
|
||||||
break
|
break
|
||||||
|
@ -242,7 +247,7 @@ func unroll_track(buffer: StreamPeerBuffer, bgm_start_pos: int, track_start_pos:
|
||||||
target_pos += 0x010000
|
target_pos += 0x010000
|
||||||
var event_num = rom_address_to_event_index.get(target_pos, -1)
|
var event_num = rom_address_to_event_index.get(target_pos, -1)
|
||||||
if event_num >= 0:
|
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]])
|
events.append([EventType.GOTO, rom_address_to_event_index[target_pos]])
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
|
@ -253,10 +258,10 @@ func unroll_track(buffer: StreamPeerBuffer, bgm_start_pos: int, track_start_pos:
|
||||||
event_idx += 1
|
event_idx += 1
|
||||||
# DEBUG: Report if track doesn't end all the contained loops
|
# DEBUG: Report if track doesn't end all the contained loops
|
||||||
if loop_level >= 0:
|
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
|
# DEBUG: Report total note duration and repeat note duration
|
||||||
if events:
|
if events:
|
||||||
print('%d events' % len(events))
|
print_info('%d events' % len(events))
|
||||||
var total_note_dur := 0
|
var total_note_dur := 0
|
||||||
var total_notes := 0
|
var total_notes := 0
|
||||||
var total_rests := 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):
|
for e in events.slice(events[-1][1], -1):
|
||||||
if e[0] == EventType.NOTE:
|
if e[0] == EventType.NOTE:
|
||||||
repeat_note_dur += e[2]
|
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:
|
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
|
return events
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
|
@ -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])
|
|
@ -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://test/audio_system.gd" type="Script" id=1]
|
||||||
[ext_resource path="res://theme/menu_theme.tres" type="Theme" id=2]
|
[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"]
|
[node name="audio_system" type="Node2D"]
|
||||||
script = ExtResource( 1 )
|
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="."]
|
[node name="inst_buttons" type="ReferenceRect" parent="."]
|
||||||
margin_right = 348.0
|
margin_right = 348.0
|
||||||
margin_bottom = 118.0
|
margin_bottom = 118.0
|
||||||
|
@ -42,4 +62,5 @@ margin_top = 192.0
|
||||||
margin_right = 315.0
|
margin_right = 315.0
|
||||||
margin_bottom = 216.0
|
margin_bottom = 216.0
|
||||||
theme = ExtResource( 2 )
|
theme = ExtResource( 2 )
|
||||||
|
pressed = true
|
||||||
text = "HACK: Loop Extension"
|
text = "HACK: Loop Extension"
|
||||||
|
|
Loading…
Reference in New Issue