126 lines
5.1 KiB
GDScript
126 lines
5.1 KiB
GDScript
extends Control
|
|
|
|
const INPUT_TEX_WIDTH := 2048
|
|
const INPUT_FORMAT := Image.FORMAT_RGBA8 # Image.FORMAT_LA8
|
|
const INPUT_BYTES_PER_TEXEL := 4 # 2
|
|
const OUTPUT_BYTES_PER_TEXEL := 4
|
|
const OUTPUT_FRAMEBUFFER_SIZE := Vector2(4096, 4096)
|
|
const OUTPUT_WIDTH := int(OUTPUT_FRAMEBUFFER_SIZE.x)
|
|
const OUTPUT_HEIGHT := int(OUTPUT_FRAMEBUFFER_SIZE.y)
|
|
const QUAD_COLOR := PoolColorArray([Color.white, Color.white, Color.white, Color.white])
|
|
var viewport: Viewport
|
|
var render_queue: Array # of Images
|
|
var result_queue: Array # of [String, PoolByteArray]
|
|
var current_textures: Array # of ImageTextures - Needed to prevent GC before draw
|
|
var waiting_for_viewport: Array
|
|
var done_first_draw: bool
|
|
|
|
func _ready() -> void:
|
|
self.viewport = get_parent()
|
|
self.render_queue = []
|
|
self.result_queue = []
|
|
self.waiting_for_viewport = []
|
|
self.done_first_draw = false
|
|
self.current_textures = []
|
|
self.get_parent().size = OUTPUT_FRAMEBUFFER_SIZE
|
|
self.material.set_shader_param('OUTPUT_FRAMEBUFFER_SIZE', OUTPUT_FRAMEBUFFER_SIZE)
|
|
self.material.set_shader_param('INT_OUTPUT_WIDTH', OUTPUT_WIDTH)
|
|
|
|
func push_image(img: Image, target_samples: int = -1, desc: String = '') -> void:
|
|
var target_rows = ceil(target_samples/float(OUTPUT_WIDTH))
|
|
if target_samples <= 0:
|
|
target_rows = int(img.get_size().y)
|
|
self.render_queue.append([img, target_rows, desc])
|
|
|
|
func push_bytes(data: PoolByteArray, target_samples: int = -1, desc: String = '') -> void:
|
|
var rows = int(pow(2, ceil(log((len(data)/INPUT_BYTES_PER_TEXEL) / INPUT_TEX_WIDTH)/log(2))))
|
|
var target_length = rows * INPUT_BYTES_PER_TEXEL * INPUT_FORMAT
|
|
while len(data) < target_length: # This is inefficient, but this function should be called with pre-padded data anyway
|
|
data.append(0)
|
|
var image := Image.new()
|
|
image.create_from_data(INPUT_TEX_WIDTH, rows, false, INPUT_FORMAT, data)
|
|
var target_rows = ceil(target_samples/float(OUTPUT_WIDTH))
|
|
if target_samples <= 0:
|
|
target_rows = rows
|
|
self.render_queue.append([image, target_rows, desc])
|
|
|
|
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
|
|
|
|
self.waiting_for_viewport = []
|
|
var rows_drawn := 0
|
|
while self.render_queue:
|
|
var draw_rows: int = self.render_queue[0][1]
|
|
rows_drawn += draw_rows
|
|
if rows_drawn > OUTPUT_HEIGHT:
|
|
if self.waiting_for_viewport.empty():
|
|
print('Could not fit %s into %dx%d output framebuffer, it needs %d rows'%[self.render_queue[0][2], OUTPUT_WIDTH, OUTPUT_HEIGHT, draw_rows])
|
|
self.render_queue.pop_front()
|
|
break
|
|
|
|
# Draw the next ImageTexture
|
|
var image_and_uv_rows_and_desc = self.render_queue.pop_front()
|
|
var i := len(self.waiting_for_viewport)
|
|
if len(self.current_textures) < i+1:
|
|
self.current_textures.append(ImageTexture.new())
|
|
var tex: ImageTexture = self.current_textures[i]
|
|
tex.create_from_image(image_and_uv_rows_and_desc[0], 0)
|
|
self.material.set_shader_param('midi_events_size', tex.get_size())
|
|
var y_top: int = OUTPUT_HEIGHT - rows_drawn
|
|
var y_bot: int = y_top + draw_rows
|
|
var uv_inv_v: float = 1 - (draw_rows / OUTPUT_FRAMEBUFFER_SIZE.y)
|
|
var uvs := PoolVector2Array([Vector2(0, uv_inv_v), Vector2(1, uv_inv_v), Vector2(1, 1), Vector2(0, 1)])
|
|
var points := PoolVector2Array([Vector2(0, y_top), Vector2(OUTPUT_WIDTH, y_top), Vector2(OUTPUT_WIDTH, y_bot), Vector2(0, y_bot)])
|
|
draw_primitive(points, QUAD_COLOR, uvs, tex)
|
|
self.waiting_for_viewport.append([draw_rows, image_and_uv_rows_and_desc[2]]) # 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()
|
|
|
|
var retrieved_rows := 0
|
|
for rows_and_desc in self.waiting_for_viewport:
|
|
var entry_rows: int = rows_and_desc[0]
|
|
var entry_desc: String = rows_and_desc[1]
|
|
var bytes_start := retrieved_rows * OUTPUT_WIDTH * OUTPUT_BYTES_PER_TEXEL
|
|
var bytes_end := (retrieved_rows + entry_rows) * OUTPUT_WIDTH * OUTPUT_BYTES_PER_TEXEL
|
|
var entry_bytes := result_bytes.subarray(bytes_start, bytes_end-1)
|
|
self.result_queue.append([entry_desc, entry_bytes])
|
|
retrieved_rows += entry_rows
|
|
# result_bytes.resize(result_byte_count)
|
|
self.waiting_for_viewport = []
|
|
|
|
# # 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)
|
|
|
|
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])
|