HeavyThing - tui_render.inc

Jeff Marrison

Table of functions

	; ------------------------------------------------------------------------
	; HeavyThing x86_64 assembly language library and showcase programs
	; Copyright © 2015-2018 2 Ton Digital 
	; Homepage: https://2ton.com.au/
	; Author: Jeff Marrison <jeff@2ton.com.au>
	;       
	; This file is part of the HeavyThing library.
	;       
	; HeavyThing is free software: you can redistribute it and/or modify
	; it under the terms of the GNU General Public License, or
	; (at your option) any later version.
	;       
	; HeavyThing is distributed in the hope that it will be useful, 
	; but WITHOUT ANY WARRANTY; without even the implied warranty of
	; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
	; GNU General Public License for more details.
	;       
	; You should have received a copy of the GNU General Public License along
	; with the HeavyThing library. If not, see <http://www.gnu.org/licenses/>.
	; ------------------------------------------------------------------------
	;       
	; tui_render.inc: one layer removed from tui_object, this is meant to be
	; the "base" layer (e.g. terminal, ssh server, etc descend from this, not
	; displayobject itself)
	;
	; this is responsible for generating the actual ansi output
	;

if used tui_render$default_vtable | used tui_render$vtable | defined include_everything

	; NOTE: we add render-only virtual methods to our tui_object ones
dalign
tui_render$default_vtable:
        dq      tui_render$cleanup, tui_object$clone, tui_object$draw, tui_object$redraw, tui_render$updatedisplaylist, tui_object$sizechanged
        dq      tui_object$timer, tui_render$layoutchanged, tui_object$move, tui_object$setfocus, tui_object$gotfocus, tui_object$lostfocus
        dq      tui_object$keyevent, tui_object$domodal, tui_object$endmodal, tui_object$exit, tui_object$calcbounds, tui_object$calcchildbounds
        dq      tui_object$appendchild, tui_object$appendbastard, tui_object$prependchild, tui_object$contains, tui_object$getchildindex
        dq      tui_object$removechild, tui_object$removebastard, tui_object$removeallchildren, tui_object$removeallbastards
        dq      tui_object$getobjectsunderpoint, tui_object$flatten, tui_object$firekeyevent, tui_object$ontab, tui_object$onshifttab
        dq      tui_render$setcursor, tui_render$showcursor, tui_render$hidecursor, tui_object$click, tui_object$clicked
	; our render-specific additions:
	dq	tui_render$ansioutput, tui_render$newlayoutonresize, tui_render$newwindowsize

end if

tui_vansioutput = tui_vclicked + 8
tui_vnewlayoutonresize = tui_vclicked + 16
tui_vnewwindowsize = tui_vclicked + 24

	; we also add a few render specific variables

tui_noupdate_ofs = tui_object_size		; dd
tui_noupdatecount_ofs = tui_object_size + 8
tui_outputbytes_ofs = tui_object_size + 16
tui_lastrender_text_ofs = tui_object_size + 24
tui_lastrender_attr_ofs = tui_object_size + 32
tui_cursor_ofs = tui_object_size + 40
tui_cursorvisible_ofs = tui_object_size + 48	; dd
tui_outputbuffer_ofs = tui_object_size + 56

tui_render_size = tui_object_size + 64


	; similar to tui_object itself, this is not meant to be constructed by itself
	; because this doesn't actually DO anything with its output
	; one more layer on top of this is required (terminal, telnet, ssh, etc)

	; ALL DESCENDENTS _MUST_ set their own vtable! (like tui_object, we skip it)

if used tui_render$init_defaults | defined include_everything
	; single parameter in rdi: the tui_render object
falign
tui_render$init_defaults:
	prolog	tui_render$init_defaults
	push	rdi
	add	rdi, 8		; skip the vtable pointer
	xor	esi, esi
	mov	edx, tui_render_size - 8
	call	memset32
	mov	rdi, [rsp]
	; copy of the tui_object defaults so we don't have to call it
	mov	dword [rdi+tui_visible_ofs], 1
	mov	dword [rdi+tui_includeinlayout_ofs], 1
	mov	dword [rdi+tui_absolutex_ofs], -1
	mov	dword [rdi+tui_absolutey_ofs], -1
	call	list$new
	mov	rdi, [rsp]
	mov	[rdi+tui_children_ofs], rax
	call	list$new
	mov	rdi, [rsp]
	mov	[rdi+tui_bastards_ofs], rax
	mov	dword [rdi+tui_cursorvisible_ofs], 1
	call	buffer$new
	mov	rdi, [rsp]
	mov	[rdi+tui_outputbuffer_ofs], rax
	pop	rdi
	epilog
end if


if used tui_render$init_copy | defined include_everything
	; two parameters: rdi == tui render we are initialising, rsi == source tui render
	; similar again to tui_object's, we copy all static settings, create new
	; buffers if width and height nonzero, create new lists, outputbuffer
	; and clear other sensibles
falign
tui_render$init_copy:
	prolog	tui_render$init_copy
	push	rsi rdi
	add	rdi, tui_object_size
	add	rsi, tui_object_size
	mov	edx, tui_render_size - tui_object_size
	call	memcpy
	mov	rdi, [rsp]
	mov	rsi, [rsp+8]
	call	tui_object$init_copy
	call	buffer$new
	xor	ecx, ecx
	mov	rdi, [rsp]
	mov	[rdi+tui_noupdate_ofs], rcx
	mov	[rdi+tui_outputbytes_ofs], rcx
	mov	[rdi+tui_lastrender_text_ofs], rcx
	mov	[rdi+tui_lastrender_attr_ofs], rcx
	mov	[rdi+tui_outputbuffer_ofs], rax
	pop	rdi rsi
	epilog
end if


if used tui_render$cleanup | defined include_everything
	; single parameter in rdi: tui render object to cleanup after:
	; NOTE: this does not free the pointer itself, but heap$free's everything that we allocated
falign
tui_render$cleanup:
	prolog	tui_render$cleanup
	push	rdi
	call	tui_object$cleanup
	mov	rsi, [rsp]
	mov	rdi, [rsi+tui_lastrender_text_ofs]
	test	rdi, rdi
	jz	.notextbuffer
	call	heap$free
	mov	rsi, [rsp]
calign
.notextbuffer:
	mov	rdi, [rsi+tui_lastrender_attr_ofs]
	test	rdi, rdi
	jz	.noattrbuffer
	call	heap$free
	mov	rsi, [rsp]
calign
.noattrbuffer:
	mov	rdi, [rsi+tui_outputbuffer_ofs]
	call	buffer$destroy
	pop	rdi
	call	tui_lock$clear
	epilog
end if


if used tui_render$updatedisplaylist | defined include_everything
	; single argument in rdi: our tui_render object
falign
tui_render$updatedisplaylist:
	prolog	tui_render$updatedisplaylist
	cmp	dword [rdi+tui_noupdate_ofs], 1
	je	.noupdates
	cmp	dword [_tui_lock], 0
	jne	.locked
	push	rdi
	mov	rsi, [rdi]		; load our vtable
	call	qword [rsi+tui_vflatten]	; call our flatten method
	test	rax, rax
	jz	.gotbupkiss
	mov	rdi, [rsp]		; get our object back
	mov	rsi, rax		; text returned from flatten
	; rdx already contains our attr returned from flatten
	call	tui_render$nvrender
	pop	rdi
	epilog
calign
.gotbupkiss:
	; flatten gave us a null return, not much else to do
	pop	rdi
	epilog
calign
.noupdates:
	; noupdate is set, so all we have to do is increase our updatecount and continue
	add	qword [rdi+tui_noupdatecount_ofs], 1
	epilog
calign
.locked:
	call	tui_lock$updatedisplaylist
	epilog
end if


if used tui_render$layoutchanged | defined include_everything
	; single argument in rdi: our tui_render object
	; we override this one because we are the parent/base and our size is known
falign
tui_render$layoutchanged:
	prolog	tui_render$layoutchanged
	push	rdi
	mov	rdi, [rdi+tui_children_ofs]	; load up our child list
	mov	rsi, .childbounds
	call	list$foreach
	xor	ecx, ecx
	mov	rdi, [rsp]
	mov	[rdi+tui_noupdatecount_ofs], rcx
	mov	dword [rdi+tui_noupdate_ofs], 1	; disable actual updates during our redraw
						; if we DON'T do this, then redraw would do a LOT of extra work for no reason
	mov	rsi, [rdi]			; load our vtable
	call	qword [rsi+tui_vredraw]		; call our redraw virtual method
	mov	rdi, [rsp]
	mov	dword [rdi+tui_noupdate_ofs], 0	; enable updates again
	cmp	qword [rdi+tui_noupdatecount_ofs], 0
	je	.nothingtodo
	; otherwise, call our updatedisplaylist virtual method, because we did have some goodies
	mov	rsi, [rdi]			; load our vtable
	call	qword [rsi+tui_vupdatedisplaylist]
	pop	rdi
	epilog
calign
.nothingtodo:
	pop	rdi
	epilog
calign
.childbounds:
	mov	rsi, [rdi]			; get the child's vtable loaded up
	call	qword [rsi+tui_vcalcbounds]
	ret
end if

if used tui_render$newlayoutonresize | defined include_everything
	; single argument in rdi: our tui_render object
	; this is the same as the above layoutchanged, only we blast lastrender_*
falign
tui_render$newlayoutonresize:
	prolog	tui_render$newlayoutonresize
	push	rdi
	mov	rdi, [rdi+tui_children_ofs]
	mov	rsi, .childbounds
	call	list$foreach
	mov	rsi, [rsp]
	mov	rdi, [rsi+tui_lastrender_text_ofs]
	test	rdi, rdi
	jz	.notextbuffer
	mov	qword [rsi+tui_lastrender_text_ofs], 0
	call	heap$free
	mov	rsi, [rsp]
calign
.notextbuffer:
	mov	rdi, [rsi+tui_lastrender_attr_ofs]
	test	rdi, rdi
	jz	.noattrbuffer
	mov	qword [rsi+tui_lastrender_attr_ofs], 0
	call	heap$free
calign
.noattrbuffer:
	xor	ecx, ecx
	mov	rdi, [rsp]
	mov	[rdi+tui_noupdatecount_ofs], rcx
	mov	dword [rdi+tui_noupdate_ofs], 1
	mov	rsi, [rdi]
	call	qword [rsi+tui_vredraw]
	mov	rdi, [rsp]
	mov	dword [rdi+tui_noupdate_ofs], 0
	cmp	qword [rdi+tui_noupdatecount_ofs], 0
	je	.nothingtodo
	mov	rsi, [rdi]
	call	qword [rsi+tui_vupdatedisplaylist]
	pop	rdi
	epilog
calign
.nothingtodo:
	pop	rdi
	epilog
calign
.childbounds:
	mov	rsi, [rdi]
	call	qword [rsi+tui_vcalcbounds]
	ret
end if


if used tui_render$ansioutput | defined include_everything
	; three arguments: rdi == our tui_render object, rsi == pointer to UTF8 bytes to send out, rdx == count
	; NOTE HERE: this is our base tui_render, and as such we have no idea what transport layer we are using
	; or anything like that, so we don't do anything except die a thousand deaths... aka PEBCAK
falign
tui_render$ansioutput:
	prolog	tui_render$ansioutput
	breakpoint
	epilog
end if


if used tui_render$showcursor | defined include_everything
	; single argument in rdi: our tui_render object
falign
tui_render$showcursor:
	prolog	tui_render$showcursor
	cmp	dword [_tui_lock], 0
	jne	.doit_locked
	cmp	dword [rdi+tui_cursorvisible_ofs], 1
	je	.alreadyvisible
	mov	dword [rdi+tui_cursorvisible_ofs], 1
	mov	rsi, .showit
	mov	edx, 6
	mov	rcx, [rdi]			; load our vtable
	call	qword [rcx+tui_vansioutput]	; send it out
	epilog
calign
.alreadyvisible:
	epilog
calign
.showit:
	db	27,'[?25h'
calign
.doit_locked:
	call	tui_lock$showcursor
	epilog
end if

if used tui_render$hidecursor | defined include_everything
	; single argument in rdi: our tui_render object
falign
tui_render$hidecursor:
	prolog	tui_render$hidecursor
	cmp	dword [_tui_lock], 0
	jne	.doit_locked
	cmp	dword [rdi+tui_cursorvisible_ofs], 0
	je	.alreadyhidden
	mov	dword [rdi+tui_cursorvisible_ofs], 0
	mov	rsi, .hideit
	mov	edx, 6
	mov	rcx, [rdi]			; load our vtable
	call	qword [rcx+tui_vansioutput]	; send it out
	epilog
calign
.alreadyhidden:
	epilog
calign
.hideit:
	db	27,'[?25l'
calign
.doit_locked:
	call	tui_lock$hidecursor
	epilog
end if

if used tui_render$setcursor | defined include_everything
	; three arguments: rdi == our tui_render object, esi == x, edx == y
	; NOTE: unlike ANSI, our cursor positions are 0 offset, so we +1 both of them here
	; prior to output
falign
tui_render$setcursor:
	prolog	tui_render$setcursor
	; if esi and edx are already the same as our cursor, we don't do anything
	cmp	esi, [rdi+tui_cursor_ofs]
	jne	.doit
	cmp	edx, [rdi+tui_cursor_ofs+4]
	jne	.doit
	epilog
calign
.doit:
	mov	[rdi+tui_cursor_ofs], esi
	mov	[rdi+tui_cursor_ofs+4], edx	; set the cursor position in our object so render knows where it goes
	cmp	dword [_tui_lock], 0
	jne	.doit_locked
	add	esi, 1
	add	edx, 1				; correct 1-offset ANSI coords
	sub	rsp, 128
	mov	[rsp], rdi
	mov	[rsp+8], rsi
	mov	[rsp+16], rdx
	mov	byte [rsp+48], 27
	mov	byte [rsp+49], '['
	mov	edi, edx			; y is first
	mov	esi, 10
	call	string$from_int
	mov	[rsp+24], rax
	mov	rdi, rax
	mov	rsi, rsp
	add	rsi, 50
	call	string$to_utf8
	mov	rdi, [rsp+24]
	mov	rax, [rdi]
	mov	[rsp+24], rax			; its length (and since we know it is all digits, utf8 length is samesame)
	call	heap$free
	mov	rdi, [rsp+8]			; x is next
	mov	esi, 10
	call	string$from_int
	mov	[rsp+32], rax
	mov	rcx, [rsp+24]			; length of the y string
	mov	rdi, rax
	mov	rsi, rsp
	add	rsi, 50
	add	rsi, rcx
	mov	byte [rsi], ';'
	add	rsi, 1
	call	string$to_utf8
	mov	rdi, [rsp+32]
	mov	rax, [rdi]
	mov	[rsp+32], rax			; its length
	call	heap$free
	mov	rax, [rsp+24]
	mov	rcx, [rsp+32]
	mov	rdi, [rsp]			; our object
	mov	rsi, rsp
	add	rsi, 48
	mov	rdx, rsi
	add	rdx, 3
	add	rdx, rax
	add	rdx, rcx
	mov	byte [rdx], 'H'
	add	rdx, 1
	sub	rdx, rsi
	mov	rcx, [rdi]			; our object's vtable
	call	qword [rcx+tui_vansioutput]
	add	rsp, 128
	epilog
calign
.doit_locked:
	call	tui_lock$setcursor
	epilog
end if

if used tui_render$newwindowsize | defined include_everything
	; three arguments: rdi == our tui_render object, esi == xsize, edx == ysize
falign
tui_render$newwindowsize:
	prolog	tui_render$newwindowsize
	xor	ecx, ecx
	push	rdi
	mov	qword [rdi+tui_bounds_ofs], rcx	; a.x and a.y == 0
	mov	dword [rdi+tui_bounds_bx_ofs], esi
	mov	dword [rdi+tui_bounds_by_ofs], edx
	mov	dword [rdi+tui_width_ofs], esi
	mov	dword [rdi+tui_height_ofs], edx
	mov	dword [rdi+tui_noupdate_ofs], 1
	mov	qword [rdi+tui_noupdatecount_ofs], rcx

	; it is possible that some child of ours might instantiate a new layout
	; and if that happens, we _must_ clear lasttext/lastattr _before_ we go anywhere
	push	qword [rdi+tui_lastrender_text_ofs]
	push	qword [rdi+tui_lastrender_attr_ofs]
	mov	qword [rdi+tui_lastrender_text_ofs], 0
	mov	qword [rdi+tui_lastrender_attr_ofs], 0

	mov	rsi, [rdi]			; our object's vtable
	call	qword [rsi+tui_vsizechanged]	; because this calls updateDisplayList otherwise by calling all of its children's draw methods

	pop	rdi
	test	rdi, rdi
	jz	.nofree
	call	heap$free
calign
.nofree:
	pop	rdi
	test	rdi, rdi
	jz	.nofree2
	call	heap$free
calign
.nofree2:
	mov	rdi, [rsp]
	mov	dword [rdi+tui_noupdate_ofs], 0
	mov	rsi, [rdi]			; our object's vtable
	call	qword [rsi+tui_vnewlayoutonresize]
	pop	rdi
	epilog

end if




if used tui_render$nvrender | defined include_everything
	; three arguments: rdi == our tui_render object, rsi == render text, rdx == render attr
falign
tui_render$nvrender:
	prolog	tui_render$nvrender
	sub	rsp, 160
	mov	[rsp], rdi
	mov	[rsp+8], rsi
	mov	[rsp+16], rdx
	; we are going to make use of all of our callee-saves
	mov	[rsp+24], rbx
	mov	[rsp+32], r12
	mov	[rsp+40], r13
	mov	[rsp+48], r14
	mov	[rsp+56], r15
	mov	[rsp+64], rbp
	mov	dword [rsp+72], 0xffffffff		; last_attr

	mov	rbp, [rdi+tui_outputbuffer_ofs]		; save the output buffer in rbp
	mov	r12, rsi				; render text
	mov	r13, rdx				; render attr
	xor	r14d, r14d				; x = 0
	mov	r15d, [rdi+tui_width_ofs]		; save our actual width so we can maintain cursor position

	mov	eax, r15d
	mov	ecx, dword [rdi+tui_height_ofs]
	mul	ecx
	mov	ebx, eax				; # of loop iterations

	mov	rdi, rbp
	call	buffer$reset

	mov	rdi, rbp
	mov	esi, ebx
	shl	esi, 2
	add	esi, 2048
	call	buffer$reserve

	mov	rdi, [rsp]
	mov	r10, [rdi+tui_lastrender_text_ofs]
	mov	r11, [rdi+tui_lastrender_attr_ofs]

	xor	ecx, ecx
	mov	dword [rsp+80], ecx			; y
	mov	dword [rsp+88], -1			; cursor_x
	mov	dword [rsp+96], -1			; cursor_y
	mov	dword [rsp+104], ecx			; inaltcharset

	test	r10, r10
	jnz	.render_loop

	; no previous render buffer exists, so send out the initial cls/setup as well
	mov	dword [rsp+88], ecx			; cursor_x = 0, since CLS puts it there for us
	mov	dword [rsp+96], ecx			; cursor_y



	mov	rdi, [rsp]
	mov	rax, .initial_cls
	mov	rcx, .initial_cls_hidecursor
	mov	r8d, .initial_cls_length
	mov	r9d, .initial_cls_hidecursor_length
	cmp	dword [rdi+tui_cursorvisible_ofs], 0
	cmove	rsi, rcx
	cmovne	rsi, rax
	cmove	edx, r9d
	cmovne	edx, r8d
	mov	rdi, rbp
	call	buffer$append

	mov	rdi, [rsp]
	mov	r10, [rdi+tui_lastrender_text_ofs]
	mov	r11, [rdi+tui_lastrender_attr_ofs]

	; so at this point:
	; ebx is our iteration number
	; rbp is our output buffer
	; r12 is our render text
	; r13 is our render attr
	; r14d is x
	; r15d is our width
	; last_attr is at [rsp+72]
	; y is at [rsp+80]
	; cursor_x is at [rsp+88]
	; cursor_y is at [rsp+96]
	; inaltcharset is at [rsp+104]
calign
.render_loop:
	test	r10, r10
	jz	.render_loop_noprior
	; otherwise, we do have a previous
	mov	eax, dword [r11]			; our old attribute
	cmp	eax, dword [r13]			; our new attribute
	jne	.render_loop_noprior
	mov	eax, dword [r10]			; our old text
	cmp	eax, dword [r12]			; our new text
	jne	.render_loop_noprior
	; otherwise, both the text and attribute are the same as they were before
	; add	r10, 4  <-- render_loop_next increments this one for us
	add	r11, 4
	; add	r12, 4	<-- render_loop_next increments this one for us
	add	r13, 4
	; in addition, render_loop_next takes care of moving x forward
	jmp	.render_loop_next
calign
.render_loop_noprior:
	mov	eax, dword [r13]			; get our attribute
	cmp	eax, dword [rsp+72]
	je	.render_loop_sameattr
	; else, this attribute was different to our last, send out the goods
	shr	eax, 8
	mov	ecx, dword [rsp+72]
	shr	ecx, 8
	and	eax, 0xff
	and	ecx, 0xff
	cmp	eax, ecx
	je	.render_loop_fgsame
	; else, fg in eax is different, we need a string version of it
	mov	[rsp+112], rax
	mov	[rsp+120], r10
	mov	[rsp+128], r11
	mov	rdi, rbp
	mov	rsi, .fgcolor_preface
	mov	edx, 7
	call	buffer$append

	mov	rax, [rsp+112]
	shl	rax, 3
	add	rax, .uchars

	mov	rdi, [rbp+buffer_endptr_ofs]
	mov	edx, dword [rax]
	mov	esi, dword [rax+4]
	mov	dword [rdi], esi
	add	qword [rbp+buffer_endptr_ofs], rdx
	add	qword [rbp+buffer_length_ofs], rdx

	mov	r10, [rsp+120]
	mov	r11, [rsp+128]
	mov	rdx, [rbp+buffer_endptr_ofs]
	mov	byte [rdx], 'm'
	add	rdx, 1
	add	qword [rbp+buffer_endptr_ofs], 1
	add	qword [rbp+buffer_length_ofs], 1
calign
.render_loop_fgsame:
	mov	eax, dword [r13]			; get our attribute again
	mov	ecx, dword [rsp+72]
	and	eax, 0xff
	and	ecx, 0xff
	cmp	eax, ecx
	je	.render_loop_bgsame
	; else, bg in eax is different, we need a string version of it
	mov	[rsp+112], rax
	mov	[rsp+120], r10
	mov	[rsp+128], r11
	mov	rdi, rbp
	mov	rsi, .bgcolor_preface
	mov	edx, 7
	call	buffer$append
	mov	rax, [rsp+112]
	shl	rax, 3
	add	rax, .uchars

	mov	rdi, [rbp+buffer_endptr_ofs]
	mov	edx, dword [rax]
	mov	esi, dword [rax+4]
	mov	dword [rdi], esi
	add	qword [rbp+buffer_endptr_ofs], rdx
	add	qword [rbp+buffer_length_ofs], rdx

	mov	r10, [rsp+120]
	mov	r11, [rsp+128]
	mov	rdx, [rbp+buffer_endptr_ofs]
	mov	byte [rdx], 'm'
	add	rdx, 1
	add	qword [rbp+buffer_endptr_ofs], 1
	add	qword [rbp+buffer_length_ofs], 1
calign
.render_loop_bgsame:
	mov	eax, dword [r13]
	mov	dword [rsp+72], eax			; last_attr = current attr
calign
.render_loop_sameattr:
	mov	r8d, [rsp+80]				; load up y
	mov	rdx, [rbp+buffer_endptr_ofs]
	add	r13, 4					; next attribute position
	add	r11, 4					; old attribute pos, we don't test it for nonzero so it is okay if we increment it regardless
	mov	eax, dword [r12]			; get our text

	; hmmm, I had these next two lines commented out from before, but I don't remember why...
	test	eax, eax				; we dont send nulls
	jz	.render_loop_next

if acs_linechars
	cmp	dword [rsp+104], 0			; inaltcharset?
	je	.render_loop_notinacs
	; else, we are in the ACS
	cmp	eax, 0x2500
	jb	.render_loop_clearacs
	cmp	eax, 0x257f
	ja	.render_loop_clearacs
end if
	; r14d is x
	; r15d is our width
	; last_attr is at [rsp+72]
	; y is at [rsp+80]
	; cursor_x is at [rsp+88]
	; cursor_y is at [rsp+96]
	; inaltcharset is at [rsp+104]
calign
.render_loop_cursorpos:
	; if (cursor_x != x || cursor_y != y)
	cmp	r14d, [rsp+88]
	jne	.render_loop_xdiff
	cmp	r8d, [rsp+96]
	jne	.render_loop_xsameydiff
calign
.render_loop_cursorokay:
	; else, x == cursor_x and y == cursor_y
	cmp	eax, 0x80
	jb	.render_loop_ascii
	cmp	eax, 0x800
	jb	.render_loop_twobyte
if acs_linechars
	cmp	eax, 0x2500
	jb	.render_loop_threebyte
	cmp	eax, 0x250c
	je	.render_loop_lc1
	cmp	eax, 0x2514
	je	.render_loop_lc2
	cmp	eax, 0x2510
	je	.render_loop_lc3
	cmp	eax, 0x2518
	je	.render_loop_lc4
	cmp	eax, 0x2500
	je	.render_loop_lc5
	cmp	eax, 0x2502
	je	.render_loop_lc6
	cmp	eax, 0x253c
	je	.render_loop_lc7
	cmp	eax, 0x251c
	je	.render_loop_lc8
	cmp	eax, 0x2524
	je	.render_loop_lc9
	cmp	eax, 0x252c
	je	.render_loop_lc10
	cmp	eax, 0x2534
	je	.render_loop_lc11
	cmp	eax, 0x2592
	je	.render_loop_lc12
end if
	cmp	eax, 0x10000
	jb	.render_loop_threebyte
	cmp	eax, 0x200000
	jb	.render_loop_fourbyte
	cmp	eax, 0x4000000
	jb	.render_loop_fivebyte
	; anything else is really invalid, but we'll go ahead and stick it in as a 6 byte sequence UCS4 style
	mov	ecx, eax
	shr	ecx, 30
	and	ecx, 1
	or	ecx, 0xfc
	mov	byte [rdx], cl
	mov	ecx, eax
	shr	ecx, 24
	and	ecx, 0x3f
	or	ecx, 0x80
	mov	byte [rdx+1], cl
	mov	ecx, eax
	shr	ecx, 18
	and	ecx, 0x3f
	or	ecx, 0x80
	mov	byte [rdx+2], cl
	mov	ecx, eax
	shr	ecx, 12
	and	ecx, 0x3f
	or	ecx, 0x80
	mov	byte [rdx+3], cl
	mov	ecx, eax
	shr	ecx, 6
	and	ecx, 0x3f
	or	ecx, 0x80
	mov	byte [rdx+4], cl
	mov	ecx, eax
	and	ecx, 0x3f
	or	ecx, 0x80
	mov	byte [rdx+5], cl
	add	rdx, 6
	add	qword [rbp+buffer_endptr_ofs], 6
	add	qword [rbp+buffer_length_ofs], 6
	jmp	.render_loop_next_cursornext
calign
.render_loop_fivebyte:
	mov	ecx, eax
	shr	ecx, 24
	and	ecx, 0x03
	or	ecx, 0xf8
	mov	byte [rdx], cl
	mov	ecx, eax
	shr	ecx, 18
	and	ecx, 0x3f
	or	ecx, 0x80
	mov	byte [rdx+1], cl
	mov	ecx, eax
	shr	ecx, 12
	and	ecx, 0x3f
	or	ecx, 0x80
	mov	byte [rdx+2], cl
	mov	ecx, eax
	shr	ecx, 6
	and	ecx, 0x3f
	or	ecx, 0x80
	mov	byte [rdx+3], cl
	mov	ecx, eax
	and	ecx, 0x3f
	or	ecx, 0x80
	mov	byte [rdx+4], cl
	add	rdx, 5
	add	qword [rbp+buffer_endptr_ofs], 5
	add	qword [rbp+buffer_length_ofs], 5
	jmp	.render_loop_next_cursornext
calign
.render_loop_fourbyte:
	mov	ecx, eax
	shr	ecx, 18
	and	ecx, 0x07
	or	ecx, 0xf0
	mov	byte [rdx], cl
	mov	ecx, eax
	shr	ecx, 12
	and	ecx, 0x3f
	or	ecx, 0x80
	mov	byte [rdx+1], cl
	mov	ecx, eax
	shr	ecx, 6
	and	ecx, 0x3f
	or	ecx, 0x80
	mov	byte [rdx+2], cl
	mov	ecx, eax
	and	ecx, 0x3f
	or	ecx, 0x80
	mov	byte [rdx+3], cl
	add	rdx, 4
	add	qword [rbp+buffer_endptr_ofs], 4
	add	qword [rbp+buffer_length_ofs], 4
	jmp	.render_loop_next_cursornext
calign
.render_loop_threebyte:
	mov	ecx, eax
	shr	ecx, 12
	and	ecx, 0x0f
	or	ecx, 0xe0
	mov	byte [rdx], cl
	mov	ecx, eax
	shr	ecx, 6
	and	ecx, 0x3f
	or	ecx, 0x80
	mov	byte [rdx+1], cl
	mov	ecx, eax
	and	ecx, 0x3f
	or	ecx, 0x80
	mov	byte [rdx+2], cl
	add	rdx, 3
	add	qword [rbp+buffer_endptr_ofs], 3
	add	qword [rbp+buffer_length_ofs], 3
	jmp	.render_loop_next_cursornext
calign
.render_loop_twobyte:
	mov	ecx, eax
	shr	ecx, 6
	and	ecx, 0x1f
	or	ecx, 0xc0
	mov	byte [rdx], cl
	mov	ecx, eax
	and	ecx, 0x3f
	or	ecx, 0x80
	mov	byte [rdx+1], cl
	add	rdx, 2
	add	qword [rbp+buffer_endptr_ofs], 2
	add	qword [rbp+buffer_length_ofs], 2
	jmp	.render_loop_next_cursornext
calign
.render_loop_ascii:
	mov	byte [rdx], al
	add	rdx, 1
	add	qword [rbp+buffer_endptr_ofs], 1
	add	qword [rbp+buffer_length_ofs], 1
	jmp	.render_loop_next_cursornext
if acs_linechars
calign
.render_loop_lc1:
	mov	eax, 0x6c
	jmp	.render_loop_ascii
calign
.render_loop_lc2:
	mov	eax, 0x6d
	jmp	.render_loop_ascii
calign
.render_loop_lc3:
	mov	eax, 0x6b
	jmp	.render_loop_ascii
calign
.render_loop_lc4:
	mov	eax, 0x6a
	jmp	.render_loop_ascii
calign
.render_loop_lc5:
	mov	eax, 0x71
	jmp	.render_loop_ascii
calign
.render_loop_lc6:
	mov	eax, 0x78
	jmp	.render_loop_ascii
calign
.render_loop_lc7:
	mov	eax, 0x6e
	jmp	.render_loop_ascii
calign
.render_loop_lc8:
	mov	eax, 0x74
	jmp	.render_loop_ascii
calign
.render_loop_lc9:
	mov	eax, 0x75
	jmp	.render_loop_ascii
calign
.render_loop_lc10:
	mov	eax, 0x77
	jmp	.render_loop_ascii
calign
.render_loop_lc11:
	mov	eax, 0x76
	jmp	.render_loop_ascii
calign
.render_loop_lc12:
	mov	eax, 0x61
	jmp	.render_loop_ascii
calign
.render_loop_clearacs:
	mov	dword [rsp+104], 0			; inaltcharset = false
	mov	byte [rdx], 27
	mov	byte [rdx+1], '('
	mov	byte [rdx+2], 'B'			; TODO: change these to dword moves
	add	rdx, 3
	add	qword [rbp+buffer_endptr_ofs], 3
	add	qword [rbp+buffer_length_ofs], 3
	jmp	.render_loop_cursorpos
calign
.render_loop_notinacs:
	cmp	eax, 0x2500
	jb	.render_loop_cursorpos
	cmp	eax, 0x257f
	ja	.render_loop_cursorpos
	mov	dword [rsp+104], 1			; inaltcharset = true
	mov	byte [rdx], 27
	mov	byte [rdx+1], '('
	mov	byte [rdx+2], '0'			; TODO: change these to dword moves
	add	rdx, 3
	add	qword [rbp+buffer_endptr_ofs], 3
	add	qword [rbp+buffer_length_ofs], 3
	jmp	.render_loop_cursorpos
end if
calign
.render_loop_next_cursornext:
	; increment cursor_x, and check line wrap for cursor_y
	add	dword [rsp+88], 1			; cursor_x++
	cmp	dword [rsp+88], r15d			; cursor_x == width?
	jb	.render_loop_next
	mov	dword [rsp+88], 0			; cursor_x = 0
	add	dword [rsp+96], 1			; cursor_y++
calign
.render_loop_next:
	mov	rdx, r10
	add	rdx, 4
	test	r10, r10
	cmovnz	r10, rdx				; increment old text position only if r10 was already nonzero
	add	r12, 4					; next text position
	add	r14d, 1					; x++
	cmp	r14d, r15d				; x == width?
	je	.render_loop_next_newline
	sub	ebx, 1
	jnz	.render_loop
	jmp	.render_loop_done
calign
.render_loop_next_morebuffer:
	mov	rdi, rbp
	mov	esi, 16384
	mov	[rsp+120], r10
	mov	[rsp+128], r11
	call	buffer$reserve
	mov	rax, [rbp+buffer_size_ofs]		; ??
	mov	r10, [rsp+120]
	mov	r11, [rsp+128]
	sub	ebx, 1
	jnz	.render_loop
	jmp	.render_loop_done
calign
.render_loop_xsameydiff:
	; just y is different
	cmp	r8d, [rsp+96]
	ja	.render_loop_ydiffdown
	; else, do a cursor up for cursor_y - y
	mov	byte [rdx], 27
	mov	byte [rdx+1], '['
	add	qword [rbp+buffer_endptr_ofs], 2
	add	qword [rbp+buffer_length_ofs], 2

	mov	eax, dword [rsp+96]
	sub	eax, dword [rsp+80]
	cmp	eax, 256
	jae	.render_loop_xsameydiff_stringbased
	shl	rax, 3
	add	rax, .uchars

	mov	rdi, [rbp+buffer_endptr_ofs]
	mov	edx, dword [rax]
	mov	esi, dword [rax+4]
	mov	dword [rdi], esi
	add	qword [rbp+buffer_endptr_ofs], rdx
	add	qword [rbp+buffer_length_ofs], rdx

	jmp	.render_loop_xsameydiff_valsent
calign
.render_loop_xsameydiff_stringbased:
	mov	edi, eax
	mov	esi, 10
	mov	[rsp+120], r10
	mov	[rsp+128], r11
	call	string$from_int
	mov	[rsp+112], rax
	mov	rdi, rbp
	mov	rsi, rax
	call	buffer$append_string
	mov	rdi, [rsp+112]
	call	heap$free
	mov	r10, [rsp+120]
	mov	r11, [rsp+128]
calign
.render_loop_xsameydiff_valsent:
	mov	rdx, [rbp+buffer_endptr_ofs]
	mov	byte [rdx], 'A'
	add	rdx, 1
	add	qword [rbp+buffer_endptr_ofs], 1
	add	qword [rbp+buffer_length_ofs], 1
	mov	r8d, [rsp+80]
	mov	[rsp+96], r8d
	mov	rdx, [rbp+buffer_endptr_ofs]
	mov	eax, dword [r12]			; restore the character for cursorokay
	jmp	.render_loop_cursorokay
calign
.render_loop_ydiffdown:
	; do a cursor down for y - cursor_y
	mov	byte [rdx], 27
	mov	byte [rdx+1], '['
	add	qword [rbp+buffer_endptr_ofs], 2
	add	qword [rbp+buffer_length_ofs], 2

	mov	eax, dword [rsp+80]
	sub	eax, dword [rsp+96]
	cmp	eax, 256
	jae	.render_loop_ydiffdown_stringbased
	shl	rax, 3
	add	rax, .uchars

	mov	rdi, [rbp+buffer_endptr_ofs]
	mov	edx, dword [rax]
	mov	esi, dword [rax+4]
	mov	dword [rdi], esi
	add	qword [rbp+buffer_endptr_ofs], rdx
	add	qword [rbp+buffer_length_ofs], rdx

	jmp	.render_loop_ydiffdown_valsent
calign
.render_loop_ydiffdown_stringbased:
	mov	edi, eax
	mov	esi, 10
	mov	[rsp+120], r10
	mov	[rsp+128], r11
	call	string$from_int
	mov	[rsp+112], rax
	mov	rdi, rbp
	mov	rsi, rax
	call	buffer$append_string
	mov	rdi, [rsp+112]
	call	heap$free
	mov	r10, [rsp+120]
	mov	r11, [rsp+128]
calign
.render_loop_ydiffdown_valsent:
	mov	rdx, [rbp+buffer_endptr_ofs]
	mov	byte [rdx], 'B'
	add	rdx, 1
	add	qword [rbp+buffer_endptr_ofs], 1
	add	qword [rbp+buffer_length_ofs], 1
	mov	r8d, [rsp+80]
	mov	[rsp+96], r8d
	mov	rdx, [rbp+buffer_endptr_ofs]
	mov	eax, dword [r12]			; restore the character for cursorokay
	jmp	.render_loop_cursorokay
calign
.render_loop_xdiff:

	; NOTE NOTE NOTE: relative x cursor positioning with the way linewrap works isn't exactly right
	; and the reason for this is: if we wrote an entire line, while we update cursor_x to 0 and assume the
	; wrap has occurred, if we don't actually write another character, linewrap doesn't actually occur
	; and then forward cursor does nothing, and thus our screen ends up all jacked
	; the docs say: If the cursor is already at the edge of the screen, relative positioning has no effect
	; 
	; So, for now, if cursor_x is zero, we do a full cursor position
	;
	; someday I should give this some more thought... but this seems to work okay:

	cmp	dword [rsp+88], 0
	je	.render_loop_xdiffydiff

	cmp	r8d, [rsp+96]
	jne	.render_loop_xdiffydiff
	; else, only x is different
	cmp	r14d, [rsp+88]
	ja	.render_loop_xdiffright
	; else, do a cursor left for cursor_x - x
	mov	byte [rdx], 27
	mov	byte [rdx+1], '['
	add	qword [rbp+buffer_endptr_ofs], 2
	add	qword [rbp+buffer_length_ofs], 2

	mov	eax, dword [rsp+88]
	sub	eax, r14d
	cmp	eax, 256
	jae	.render_loop_xdiff_stringbased
	shl	rax, 3
	add	rax, .uchars

	mov	rdi, [rbp+buffer_endptr_ofs]
	mov	edx, dword [rax]
	mov	esi, dword [rax+4]
	mov	dword [rdi], esi
	add	qword [rbp+buffer_endptr_ofs], rdx
	add	qword [rbp+buffer_length_ofs], rdx

	jmp	.render_loop_xdiff_valsent
calign
.render_loop_xdiff_stringbased:
	mov	edi, eax
	mov	esi, 10
	mov	[rsp+120], r10
	mov	[rsp+128], r11
	call	string$from_int
	mov	[rsp+112], rax
	mov	rdi, rbp
	mov	rsi, rax
	call	buffer$append_string
	mov	rdi, [rsp+112]
	call	heap$free
	mov	r10, [rsp+120]
	mov	r11, [rsp+128]
calign
.render_loop_xdiff_valsent:
	mov	rdx, [rbp+buffer_endptr_ofs]
	mov	byte [rdx], 'D'
	add	rdx, 1
	add	qword [rbp+buffer_endptr_ofs], 1
	add	qword [rbp+buffer_length_ofs], 1
	mov	[rsp+88], r14d
	mov	rdx, [rbp+buffer_endptr_ofs]
	mov	eax, dword [r12]			; restore the character for cursorokay
	jmp	.render_loop_cursorokay
calign
.render_loop_xdiffright:
	; do a cursor right for x - cursor_x
	mov	byte [rdx], 27
	mov	byte [rdx+1], '['
	add	qword [rbp+buffer_endptr_ofs], 2
	add	qword [rbp+buffer_length_ofs], 2

	mov	eax, r14d
	sub	eax, dword [rsp+88]
	cmp	eax, 256
	jae	.render_loop_xdiffright_stringbased
	shl	rax, 3
	add	rax, .uchars

	mov	rdi, [rbp+buffer_endptr_ofs]
	mov	edx, dword [rax]
	mov	esi, dword [rax+4]
	mov	dword [rdi], esi
	add	qword [rbp+buffer_endptr_ofs], rdx
	add	qword [rbp+buffer_length_ofs], rdx

	jmp	.render_loop_xdiffright_valsent
calign
.render_loop_xdiffright_stringbased:
	mov	edi, eax
	mov	esi, 10
	mov	[rsp+120], r10
	mov	[rsp+128], r11
	call	string$from_int
	mov	[rsp+112], rax
	mov	rdi, rbp
	mov	rsi, rax
	call	buffer$append_string
	mov	rdi, [rsp+112]
	call	heap$free
	mov	r10, [rsp+120]
	mov	r11, [rsp+128]
calign
.render_loop_xdiffright_valsent:
	mov	rdx, [rbp+buffer_endptr_ofs]
	mov	byte [rdx], 'C'
	add	rdx, 1
	add	qword [rbp+buffer_endptr_ofs], 1
	add	qword [rbp+buffer_length_ofs], 1
	mov	[rsp+88], r14d
	mov	rdx, [rbp+buffer_endptr_ofs]
	mov	eax, dword [r12]			; restore the character for cursorokay
	jmp	.render_loop_cursorokay
	; y is at [rsp+80]
	; cursor_x is at [rsp+88]
	; cursor_y is at [rsp+96]
calign
.render_loop_xdiffydiff:
	; both are different
	mov	byte [rdx], 27
	mov	byte [rdx+1], '['
	add	qword [rbp+buffer_endptr_ofs], 2
	add	qword [rbp+buffer_length_ofs], 2

	mov	[rsp+120], r10
	mov	[rsp+128], r11				; save these just in case we do end up doing string based sends

	mov	eax, [rsp+80]				; y
	add	eax, 1					; correct to ANSI 1 offset
	cmp	eax, 256
	jae	.render_loop_xdiffydiff_ystringbased
	shl	rax, 3
	add	rax, .uchars

	mov	rdi, [rbp+buffer_endptr_ofs]
	mov	edx, dword [rax]
	mov	esi, dword [rax+4]
	mov	dword [rdi], esi
	add	qword [rbp+buffer_endptr_ofs], rdx
	add	qword [rbp+buffer_length_ofs], rdx

	jmp	.render_loop_xdiffydiff_ysent
calign
.render_loop_xdiffydiff_ystringbased:
	mov	edi, eax
	mov	esi, 10
	call	string$from_int
	mov	[rsp+112], rax
	mov	rdi, rbp
	mov	rsi, rax
	call	buffer$append_string
	mov	rdi, [rsp+112]
	call	heap$free
calign
.render_loop_xdiffydiff_ysent:
	mov	rdx, [rbp+buffer_endptr_ofs]
	mov	byte [rdx], ';'
	add	rdx, 1
	add	qword [rbp+buffer_endptr_ofs], 1
	add	qword [rbp+buffer_length_ofs], 1

	mov	eax, r14d				; x
	add	eax, 1					; correct to ANSI 1 offset
	cmp	eax, 256
	jae	.render_loop_xdiffydiff_xstringbased
	shl	rax, 3
	add	rax, .uchars

	mov	rdi, [rbp+buffer_endptr_ofs]
	mov	edx, dword [rax]
	mov	esi, dword [rax+4]
	mov	dword [rdi], esi
	add	qword [rbp+buffer_endptr_ofs], rdx
	add	qword [rbp+buffer_length_ofs], rdx

	jmp	.render_loop_xdiffydiff_xsent
calign
.render_loop_xdiffydiff_xstringbased:
	mov	edi, eax
	mov	esi, 10
	call	string$from_int
	mov	[rsp+112], rax
	mov	rdi, rbp
	mov	rsi, rax
	call	buffer$append_string
	mov	rdi, [rsp+112]
	call	heap$free
calign
.render_loop_xdiffydiff_xsent:
	mov	rdx, [rbp+buffer_endptr_ofs]
	mov	byte [rdx], 'H'
	add	rdx, 1
	add	qword [rbp+buffer_endptr_ofs], 1
	add	qword [rbp+buffer_length_ofs], 1
	mov	[rsp+88], r14d				; cursor_x = x
	mov	eax, [rsp+80]
	mov	[rsp+96], eax				; cursor_y = y
	mov	rdx, [rbp+buffer_endptr_ofs]
	mov	r10, [rsp+120]
	mov	r11, [rsp+128]
	mov	eax, dword [r12]			; restore the character for cursorokay
	jmp	.render_loop_cursorokay
calign
.render_loop_next_newline:
	xor	r14d, r14d				; x = 0
	add	dword [rsp+80], 1			; y++

	mov	rdx, [rbp+buffer_size_ofs]		; end of every line, make sure we have a bunch of room
	mov	rcx, [rbp+buffer_length_ofs]
	sub	rdx, rcx
	cmp	rdx, 16384
	jbe	.render_loop_next_morebuffer
	sub	ebx, 1
	jnz	.render_loop
calign
.render_loop_done:
	test	r10, r10
	jz	.render_loop_done_nofree

	; free last render text and attr buffers
	mov	rsi, [rsp]
	mov	rdi, [rsi+tui_lastrender_text_ofs]
	call	heap$free

	mov	rsi, [rsp]
	mov	rdi, [rsi+tui_lastrender_attr_ofs]
	call	heap$free
calign
.render_loop_done_nofree:
	; if (inaltcharset)...
	; noColor
	; cursor
	; cursor_visible
	; send
	; save last_render_text/last_render_attr
	mov	rdi, [rsp]				; get our tui_render object back
	mov	rdx, [rbp+buffer_endptr_ofs]
if acs_linechars
	cmp	dword [rsp+104], 0
	je	.render_done_noacs
	; else, we have to kick us out of ACS
	mov	byte [rdx], 27
	mov	byte [rdx+1], '('
	mov	byte [rdx+2], 'B'			; TODO: change these to dword moves
	add	rdx, 3
	add	qword [rbp+buffer_endptr_ofs], 3
	add	qword [rbp+buffer_length_ofs], 3
calign
.render_done_noacs:
end if
	; next up: nocolor setting
	mov	byte [rdx], 27
	mov	byte [rdx+1], '['
	mov	byte [rdx+2], '0'
	mov	byte [rdx+3], 'm'			; TODO: change these to dword moves
	add	rdx, 4
	add	qword [rbp+buffer_endptr_ofs], 4
	add	qword [rbp+buffer_length_ofs], 4

	; next up: place our actual render object cursor where it goes
	mov	r14d, [rdi+tui_cursor_ofs]		; cursor.x
	mov	r15d, [rdi+tui_cursor_ofs+4]		; cursor.y

	mov	byte [rdx], 27
	mov	byte [rdx+1], '['
	add	qword [rbp+buffer_endptr_ofs], 2
	add	qword [rbp+buffer_length_ofs], 2

	mov	eax, r15d				; y
	add	eax, 1					; correct to ANSI 1 offset
	cmp	eax, 256
	jae	.render_loop_done_ystringbased
	shl	rax, 3
	add	rax, .uchars

	mov	rdi, [rbp+buffer_endptr_ofs]
	mov	edx, dword [rax]
	mov	esi, dword [rax+4]
	mov	dword [rdi], esi
	add	qword [rbp+buffer_endptr_ofs], rdx
	add	qword [rbp+buffer_length_ofs], rdx

	jmp	.render_loop_done_ysent
calign
.render_loop_done_ystringbased:
	mov	edi, eax
	mov	esi, 10
	call	string$from_int
	mov	[rsp+112], rax
	mov	rdi, rbp
	mov	rsi, rax
	call	buffer$append_string
	mov	rdi, [rsp+112]
	call	heap$free
calign
.render_loop_done_ysent:
	mov	rdx, [rbp+buffer_endptr_ofs]
	mov	byte [rdx], ';'
	add	rdx, 1
	add	qword [rbp+buffer_endptr_ofs], 1
	add	qword [rbp+buffer_length_ofs], 1

	mov	eax, r14d				; x
	add	eax, 1					; correct to ANSI 1 offset
	cmp	eax, 256
	jae	.render_loop_done_xstringbased
	shl	rax, 3
	add	rax, .uchars

	mov	rdi, [rbp+buffer_endptr_ofs]
	mov	edx, dword [rax]
	mov	esi, dword [rax+4]
	mov	dword [rdi], esi
	add	qword [rbp+buffer_endptr_ofs], rdx
	add	qword [rbp+buffer_length_ofs], rdx

	jmp	.render_loop_done_xsent
calign
.render_loop_done_xstringbased:
	mov	edi, eax
	mov	esi, 10
	call	string$from_int
	mov	[rsp+112], rax
	mov	rdi, rbp
	mov	rsi, rax
	call	buffer$append_string
	mov	rdi, [rsp+112]
	call	heap$free
calign
.render_loop_done_xsent:
	mov	rdx, [rbp+buffer_endptr_ofs]
	mov	byte [rdx], 'H'
	add	rdx, 1
	add	qword [rbp+buffer_endptr_ofs], 1
	add	qword [rbp+buffer_length_ofs], 1
	mov	rdx, [rbp+buffer_endptr_ofs]

	; if the cursorvisible flag is enabled, add that too
	mov	rdi, [rsp]				; get our object back
	cmp	dword [rdi+tui_cursorvisible_ofs], 0
	je	.render_done_cursorhidden
	
	mov	byte [rdx], 27
	mov	byte [rdx+1], '['
	mov	byte [rdx+2], '?'
	mov	byte [rdx+3], '2'			; TODO: change these to dword moves
	mov	byte [rdx+4], '5'
	mov	byte [rdx+5], 'h'			; show cursor
	add	rdx, 6
	add	qword [rbp+buffer_endptr_ofs], 6
	add	qword [rbp+buffer_length_ofs], 6
calign
.render_done_cursorhidden:
	; send the buffer sitting in rbp out the door
	mov	rbx, rbp
	mov	rbp, [rsp+64]				; restore rbp


	mov	rdi, [rsp]
	mov	rsi, [rbx+buffer_itself_ofs]
	mov	rdx, [rbx+buffer_length_ofs]
	mov	rcx, [rdi]				; get the object's vtable
	call	qword [rcx+tui_vansioutput]

	mov	rdi, [rsp]				; get our object back
	mov	rcx, [rbx+buffer_length_ofs]
	add	qword [rdi+tui_outputbytes_ofs], rcx	; update how many bytes we have sent out
	; and last but not least, set last_rendertext and last_renderattr

	if defined force_full_redraws

	xor	ecx, ecx
	mov	[rdi+tui_lastrender_text_ofs], rcx
	mov	[rdi+tui_lastrender_attr_ofs], rcx

	mov	rdi, [rsp+8]
	call	heap$free
	mov	rdi, [rsp+16]
	call	heap$free

	else

	mov	rdx, [rsp+8]
	mov	rcx, [rsp+16]
	mov	[rdi+tui_lastrender_text_ofs], rdx
	mov	[rdi+tui_lastrender_attr_ofs], rcx

	end if

	; restore our callee-saves
	mov	rbx, [rsp+24]
	mov	r12, [rsp+32]
	mov	r13, [rsp+40]
	mov	r14, [rsp+48]
	mov	r15, [rsp+56]
	add	rsp, 160
	; voila.
	epilog
dalign
.initial_cls:
	db	27,'[38;5;0m',27,'[48;5;0m',27,'[H',27,'[J',27,'[1;1H',27,'[0m'
	.initial_cls_length = $ - .initial_cls
dalign
.initial_cls_hidecursor:
	db	27,'[38;5;0m',27,'[48;5;0m',27,'[H',27,'[J',27,'[1;1H',27,'[0m',27,'[?25l'
	.initial_cls_hidecursor_length = $ - .initial_cls_hidecursor
dalign
.fgcolor_preface:		; 7 bytes
	db	27,'[38;5;'
dalign
.bgcolor_preface:		; 7 bytes
	db	27,'[48;5;'

	; because a) our fgcol/bgcol is 256 limited, and because
	; most of our cursor positioning is also likely to be 256 or less
	; we create this big bloated table of predone UTF8 with dd length prefix
	; so each one take 8 bytes, x 256 == extra 2048 bytes of code here, but
	; worth it due to not having to call string$from_int repeatedly
macro ualign {
	local a
	virtual
		align 4
		a = $ - $$
	end virtual
	if a = 3
		db 0x0, 0x0, 0x0
	else if a = 2
		db 0x0, 0x0
	else if a = 1
		db 0x0
	end if
}
dalign
.uchars:
macro uchar val* {
	local TOP,AA
	ualign
	TOP:
		dd AA
		db val
	AA = ($ - TOP - 4)
}
uchar '0'
uchar '1'
uchar '2'
uchar '3'
uchar '4'
uchar '5'
uchar '6'
uchar '7'
uchar '8'
uchar '9'
uchar '10'
uchar '11'
uchar '12'
uchar '13'
uchar '14'
uchar '15'
uchar '16'
uchar '17'
uchar '18'
uchar '19'
uchar '20'
uchar '21'
uchar '22'
uchar '23'
uchar '24'
uchar '25'
uchar '26'
uchar '27'
uchar '28'
uchar '29'
uchar '30'
uchar '31'
uchar '32'
uchar '33'
uchar '34'
uchar '35'
uchar '36'
uchar '37'
uchar '38'
uchar '39'
uchar '40'
uchar '41'
uchar '42'
uchar '43'
uchar '44'
uchar '45'
uchar '46'
uchar '47'
uchar '48'
uchar '49'
uchar '50'
uchar '51'
uchar '52'
uchar '53'
uchar '54'
uchar '55'
uchar '56'
uchar '57'
uchar '58'
uchar '59'
uchar '60'
uchar '61'
uchar '62'
uchar '63'
uchar '64'
uchar '65'
uchar '66'
uchar '67'
uchar '68'
uchar '69'
uchar '70'
uchar '71'
uchar '72'
uchar '73'
uchar '74'
uchar '75'
uchar '76'
uchar '77'
uchar '78'
uchar '79'
uchar '80'
uchar '81'
uchar '82'
uchar '83'
uchar '84'
uchar '85'
uchar '86'
uchar '87'
uchar '88'
uchar '89'
uchar '90'
uchar '91'
uchar '92'
uchar '93'
uchar '94'
uchar '95'
uchar '96'
uchar '97'
uchar '98'
uchar '99'
uchar '100'
uchar '101'
uchar '102'
uchar '103'
uchar '104'
uchar '105'
uchar '106'
uchar '107'
uchar '108'
uchar '109'
uchar '110'
uchar '111'
uchar '112'
uchar '113'
uchar '114'
uchar '115'
uchar '116'
uchar '117'
uchar '118'
uchar '119'
uchar '120'
uchar '121'
uchar '122'
uchar '123'
uchar '124'
uchar '125'
uchar '126'
uchar '127'
uchar '128'
uchar '129'
uchar '130'
uchar '131'
uchar '132'
uchar '133'
uchar '134'
uchar '135'
uchar '136'
uchar '137'
uchar '138'
uchar '139'
uchar '140'
uchar '141'
uchar '142'
uchar '143'
uchar '144'
uchar '145'
uchar '146'
uchar '147'
uchar '148'
uchar '149'
uchar '150'
uchar '151'
uchar '152'
uchar '153'
uchar '154'
uchar '155'
uchar '156'
uchar '157'
uchar '158'
uchar '159'
uchar '160'
uchar '161'
uchar '162'
uchar '163'
uchar '164'
uchar '165'
uchar '166'
uchar '167'
uchar '168'
uchar '169'
uchar '170'
uchar '171'
uchar '172'
uchar '173'
uchar '174'
uchar '175'
uchar '176'
uchar '177'
uchar '178'
uchar '179'
uchar '180'
uchar '181'
uchar '182'
uchar '183'
uchar '184'
uchar '185'
uchar '186'
uchar '187'
uchar '188'
uchar '189'
uchar '190'
uchar '191'
uchar '192'
uchar '193'
uchar '194'
uchar '195'
uchar '196'
uchar '197'
uchar '198'
uchar '199'
uchar '200'
uchar '201'
uchar '202'
uchar '203'
uchar '204'
uchar '205'
uchar '206'
uchar '207'
uchar '208'
uchar '209'
uchar '210'
uchar '211'
uchar '212'
uchar '213'
uchar '214'
uchar '215'
uchar '216'
uchar '217'
uchar '218'
uchar '219'
uchar '220'
uchar '221'
uchar '222'
uchar '223'
uchar '224'
uchar '225'
uchar '226'
uchar '227'
uchar '228'
uchar '229'
uchar '230'
uchar '231'
uchar '232'
uchar '233'
uchar '234'
uchar '235'
uchar '236'
uchar '237'
uchar '238'
uchar '239'
uchar '240'
uchar '241'
uchar '242'
uchar '243'
uchar '244'
uchar '245'
uchar '246'
uchar '247'
uchar '248'
uchar '249'
uchar '250'
uchar '251'
uchar '252'
uchar '253'
uchar '254'
uchar '255'

end if