.{math, memory, dt} := @use("../../stn/src/lib.hb"); .{Color, text} := @use("lib.hb"); .{get_glyph, get_glyph_unicode, Font, UNC_TABLE_SIZE} := text; .{Vec2} := math // safety: don't use before init() or you will get a memory access violation framebuffer := memory.dangling(Color) Surface := struct { buf: ^Color, width: uint, height: uint, } new_surface := fn(width: uint, height: uint): Surface { return .( @inline(memory.alloc, Color, width * height), width, height, ) } surface_from_ptr := fn(ptr: ^Color, width: uint, height: uint): Surface { return .( ptr, width, height, ) } clone_surface := fn(surface: ^Surface): Surface { new := new_surface(surface.width, surface.height) @inline(memory.copy, Color, surface.buf, new.buf, @intcast(surface.width * surface.height)) return new } // ! is broken, check memory.free function free_surface := fn(surface: Surface): void { return @inline(memory.free, Color, surface.buf, @intcast(surface.width * surface.height), false) } init := fn(doublebuffer: bool): Surface { framebuffer = dt.get(^Color, "framebuffer/fb0/ptr\0") width := dt.get(uint, "framebuffer/fb0/width\0") height := dt.get(uint, "framebuffer/fb0/height\0") if doublebuffer { return new_surface(width, height) } else { return .(framebuffer, width, height) } } clear := fn(surface: Surface, color: Color): void { return @inline(memory.set, Color, &color, surface.buf, surface.width * surface.height) } sync := fn(surface: Surface): void { // vague safety if surface.buf == framebuffer { return } return @inline(memory.copy, Color, surface.buf, framebuffer, @bitcast(surface.width * surface.height)) } index := fn(surface: Surface, x: uint, y: uint): uint { return x + surface.width * y } indexptr := fn(surface: Surface, x: uint, y: uint): ^Color { return surface.buf + @inline(index, surface, x, y) } put_pixel := fn(surface: Surface, pos: Vec2(uint), color: Color): void { *@inline(indexptr, surface, pos.x, pos.y) = color return } put_filled_rect := fn(surface: Surface, pos: Vec2(uint), tr: Vec2(uint), color: Color): void { top_start_idx := @inline(indexptr, surface, pos.x, pos.y) bottom_start_idx := @inline(indexptr, surface, pos.x, pos.y + tr.y - 1) rows_to_fill := tr.y loop if rows_to_fill <= 1 break else { // inline is broked memory.set(Color, &color, top_start_idx, @bitcast(tr.x)) memory.set(Color, &color, bottom_start_idx, @bitcast(tr.x)) top_start_idx += surface.width bottom_start_idx -= surface.width rows_to_fill -= 2 } if rows_to_fill == 1 { @inline(memory.set, Color, &color, top_start_idx, @bitcast(tr.x)) } return } put_rect := fn(surface: Surface, pos: Vec2(uint), tr: Vec2(uint), color: Color): void { start_idx := @inline(indexptr, surface, pos.x, pos.y) end_idx := @inline(indexptr, surface, pos.x, pos.y + tr.y) right_start_idx := @inline(indexptr, surface, pos.x + tr.x, pos.y) loop if start_idx > end_idx break else { *start_idx = color; *right_start_idx = color start_idx += surface.width right_start_idx += surface.width } @inline(memory.set, Color, &color, @inline(indexptr, surface, pos.x, pos.y), @bitcast(tr.x + 1)) @inline(memory.set, Color, &color, @inline(indexptr, surface, pos.x, pos.y + tr.y), @bitcast(tr.x + 1)) return } put_line_low := fn(surface: Surface, p0: Vec2(uint), p1: Vec2(uint), color: Color): void { dx := @as(int, @bitcast(p1.x - p0.x)) dy := @as(int, @bitcast(p1.y - p0.y)) yi := 1 if dy < 0 { yi = -1 dy = -dy } D := @as(int, 2) * dy - dx y := p0.y x := p0.x loop if x == p1.x break else { *@inline(indexptr, surface, x, y) = color if D > 0 { y += yi D += 2 * (dy - dx) } else { D += 2 * dy } x += 1 } return } put_line_high := fn(surface: Surface, p0: Vec2(uint), p1: Vec2(uint), color: Color): void { dx := @as(int, @bitcast(p1.x - p0.x)) dy := @as(int, @bitcast(p1.y - p0.y)) xi := 1 if dy < 0 { xi = -1 dx = -dx } D := @as(int, 2) * dx - dy x := p0.x y := p0.y loop if y == p1.y break else { *@inline(indexptr, surface, x, y) = color if D > 0 { x += xi D += 2 * (dx - dy) } else { D += 2 * dx } y += 1 } return } put_line := fn(surface: Surface, p0: Vec2(uint), p1: Vec2(uint), color: Color): void { if math.abs(uint, p1.y - p0.y) < math.abs(uint, p1.x - p0.x) { if p0.x > p1.x { @inline(put_line_low, surface, p1, p0, color) } else { @inline(put_line_low, surface, p0, p1, color) } } else { if p0.y > p1.y { @inline(put_line_high, surface, p1, p0, color) } else { @inline(put_line_high, surface, p0, p1, color) } } return } put_surface := fn(surface: Surface, top: Surface, pos: Vec2(uint), flip_v: bool): void { top_start_idx := @inline(indexptr, surface, pos.x, pos.y) bottom_start_idx := @inline(indexptr, surface, pos.x, pos.y + top.height - 1) rows_to_copy := top.height top_cursor := top.buf bottom_cursor := top.buf + top.width * (top.height - 1) loop if rows_to_copy <= 1 break else { if flip_v { @inline(memory.copy, Color, top_cursor, bottom_start_idx, @bitcast(top.width)) @inline(memory.copy, Color, bottom_cursor, top_start_idx, @bitcast(top.width)) } else { @inline(memory.copy, Color, top_cursor, top_start_idx, @bitcast(top.width)) @inline(memory.copy, Color, bottom_cursor, bottom_start_idx, @bitcast(top.width)) } top_start_idx += surface.width bottom_start_idx -= surface.width top_cursor += top.width bottom_cursor -= top.width rows_to_copy -= 2 } if rows_to_copy == 1 { @inline(memory.copy, Color, top_cursor, top_start_idx, @bitcast(top.width)) } return } // peony-made put_trirect := fn(surface: Surface, pos: Vec2(uint), size: Vec2(int), color0: Color, color1: Color): void { step := Vec2(int).(1, 1) if size.x < 0 { step.x = -1 } if size.y < 0 { step.y /= @bitcast(size.x) } start_y := pos.y target := pos + @bitcast(size) loop if pos.x == target.x break else { @inline(put_vline, surface, pos.x, pos.y, target.y, color0) @inline(put_vline, surface, pos.x, pos.y, start_y, color1) pos += @bitcast(step) } return } // peony-made put_vline := fn(surface: Surface, x: uint, y0: uint, y1: uint, color: Color): void { if y1 < y0 { tmp := y0 y0 = y1 y1 = tmp } y := y0 loop if y == y1 break else { *@inline(indexptr, surface, x, y) = color y += 1 } return } // peony-made put_hline := fn(surface: Surface, y: uint, x0: uint, x1: uint, color: Color): void { if x1 < x0 { tmp := x0 x0 = x1 x1 = tmp } @inline(memory.set, Color, &color, @inline(indexptr, surface, x0, y), @bitcast(x1 - x0 - 1)) return } put_text := fn(surface: Surface, font: Font, pos: Vec2(uint), color: Color, str: ^u8): void { cursor := Vec2(uint).(pos.x, pos.y) current_char := str loop if *current_char == 0 break else { glyph_data := memory.dangling(u8) if font.unicode != null { code_point := @as(uint, 0) first_byte := *current_char num_bytes := 1 if (first_byte & 0x80) == 0 { code_point = first_byte } else if (first_byte & 0xE0) == 0xC0 { num_bytes = 2 code_point = first_byte & 0x1F } else if (first_byte & 0xF0) == 0xE0 { num_bytes = 3 code_point = first_byte & 0xF } else if (first_byte & 0xF8) == 0xF0 { // handle later current_char += 1 continue } else { current_char += 1 continue } valid_sequence := true i := 1 loop if i >= num_bytes break else { current_char += 1 // have to check twice due to odd compiler bug...? if *current_char == 0 | (*current_char & 0xC0) != 0x80 { valid_sequence = false } if valid_sequence == false { break } code_point = code_point << 6 | *current_char & 0x3F i += 1 } if valid_sequence == false { current_char += 1 continue } current_char += 1 if code_point == 10 { cursor.x = pos.x cursor.y += font.height + font.line_gap continue } if code_point < UNC_TABLE_SIZE { glyph_index := *(font.unicode + code_point) if glyph_index == 0xFFFF { continue } glyph_data = font.data + glyph_index * font.bytes_per_glyph } else { continue } } else { if *current_char > font.num_glyphs { continue } glyph_data = @inline(get_glyph, font, *current_char) if *current_char == 10 { cursor.x = pos.x cursor.y += font.height + font.line_gap current_char += 1 continue } current_char += 1 } if cursor.y + font.height > surface.height break if cursor.x % surface.width + font.width >= surface.width { cursor.x = pos.x cursor.y += font.height + font.line_gap } dest := @inline(indexptr, surface, cursor.x, cursor.y) rows_remaining := font.height loop if rows_remaining == 0 break else { byte := *glyph_data pixel_dest := dest mask := @as(u8, 0x80) bits_remaining := font.width loop if bits_remaining == 0 break else { if (byte & mask) != 0 { *pixel_dest = color } pixel_dest += 1 mask >>= 1 if mask == 0 & bits_remaining > 0 { glyph_data += 1 byte = *glyph_data mask = 0x80 } bits_remaining -= 1 } if mask != 0x80 { glyph_data += 1 } dest += surface.width rows_remaining -= 1 } cursor.x += font.width + font.char_gap } return }