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])