.{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, ) } 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 } 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 { 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(memory.set, Color, &color, top_start_idx, tr.x) @inline(memory.set, Color, &color, bottom_start_idx, 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, 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 { src_top_cursor := top.buf src_bottom_cursor := top.buf + top.width * (top.height - 1) dst_top_idx := @inline(indexptr, surface, pos.x, pos.y) dst_bottom_idx := @inline(indexptr, surface, pos.x, pos.y + top.height - 1) dst_increment := surface.width if flip_v { dst_increment = -dst_increment tmp := dst_top_idx dst_top_idx = dst_bottom_idx dst_bottom_idx = tmp } rows_to_copy := top.height loop if rows_to_copy <= 1 break else { @inline(memory.copy, Color, src_top_cursor, dst_top_idx, top.width) @inline(memory.copy, Color, src_bottom_cursor, dst_bottom_idx, top.width) dst_top_idx += dst_increment dst_bottom_idx -= dst_increment src_top_cursor += top.width src_bottom_cursor -= top.width rows_to_copy -= 2 } if rows_to_copy == 1 { @inline(memory.copy, Color, src_top_cursor, dst_top_idx, 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_circle := fn(surface: Surface, pos: Vec2(uint), radius: uint, color: Color): void { x := 0 y := radius error := @as(int, 3) - @as(int, @intcast(2 * radius)); *@inline(indexptr, surface, pos.x + radius, pos.y) = color; *@inline(indexptr, surface, pos.x - radius, pos.y) = color; *@inline(indexptr, surface, pos.x, pos.y + radius) = color; *@inline(indexptr, surface, pos.x, pos.y - radius) = color loop if y < x break else { x += 1 if error > 0 { y -= 1 error += 4 * (@as(int, @intcast(x)) - @as(int, @intcast(y))) + 10 } else { error += 4 * @intcast(x) + 6 }; *@inline(indexptr, surface, pos.x + x, pos.y + y) = color; *@inline(indexptr, surface, pos.x + y, pos.y + x) = color; *@inline(indexptr, surface, pos.x - x, pos.y + y) = color; *@inline(indexptr, surface, pos.x - y, pos.y + x) = color; *@inline(indexptr, surface, pos.x + x, pos.y - y) = color; *@inline(indexptr, surface, pos.x + y, pos.y - x) = color; *@inline(indexptr, surface, pos.x - x, pos.y - y) = color; *@inline(indexptr, surface, pos.x - y, pos.y - x) = color } return } put_filled_circle := fn(surface: Surface, pos: Vec2(uint), radius: uint, color: Color): void { x := 0 y := radius error := @as(int, 3) - @as(int, @intcast(2 * radius)) @inline(put_hline, surface, pos.y - x, pos.x - radius, pos.x + radius, color); *@inline(indexptr, surface, pos.x, pos.y + radius) = color; *@inline(indexptr, surface, pos.x, pos.y - radius) = color loop if y < x break else { x += 1 if error > 0 { @inline(put_hline, surface, pos.y + y, pos.x - x, pos.x + x, color) @inline(put_hline, surface, pos.y - y, pos.x - x, pos.x + x, color) y -= 1 error += 4 * (@as(int, @intcast(x)) - @as(int, @intcast(y))) + 10 } else { error += 4 * @intcast(x) + 6 } @inline(put_hline, surface, pos.y + x, pos.x - y, pos.x + y, color) @inline(put_hline, surface, pos.y - x, pos.x - y, pos.x + y, color) } return } put_textured_circle := fn(surface: Surface, source: Surface, source_pos: Vec2(uint), pos: Vec2(uint), radius: uint): void { x := 0 y := radius error := @as(int, 3) - @as(int, @intcast(2 * radius)) @inline(memory.copy, Color, @inline(indexptr, source, source_pos.x - y, source_pos.y), @inline(indexptr, surface, pos.x - y, pos.y), 2 * y); *@inline(indexptr, surface, pos.x, pos.y + y) = *@inline(indexptr, source, source_pos.x, source_pos.y + y); *@inline(indexptr, surface, pos.x, pos.y - y) = *@inline(indexptr, source, source_pos.x, source_pos.y - y) loop if y < x break else { x += 1 if error > 0 { @inline(memory.copy, Color, @inline(indexptr, source, source_pos.x - x, source_pos.y + y), @inline(indexptr, surface, pos.x - x, pos.y + y), 2 * x) @inline(memory.copy, Color, @inline(indexptr, source, source_pos.x - x, source_pos.y - y), @inline(indexptr, surface, pos.x - x, pos.y - y), 2 * x) y -= 1 error += 4 * (@as(int, @intcast(x)) - @as(int, @intcast(y))) + 10 } else { error += 4 * @intcast(x) + 6 } @inline(memory.copy, Color, @inline(indexptr, source, source_pos.x - y, source_pos.y + x), @inline(indexptr, surface, pos.x - y, pos.y + x), 2 * y) @inline(memory.copy, Color, @inline(indexptr, source, source_pos.x - y, source_pos.y - x), @inline(indexptr, surface, pos.x - y, pos.y - x), 2 * y) } return } utf8_len_table := [u8].(0, 0, 2, 3) put_text := fn(surface: Surface, font: Font, pos: Vec2(uint), color: Color, str: ^u8): void { cursor := Vec2(uint).(pos.x, pos.y) max_y := surface.height - font.height next_line_y := font.height + font.line_gap char_advance := font.width + font.char_gap surface_width := surface.width loop if *str == 0 break else { if cursor.y > max_y break glyph_data := @as(^u8, idk) code_point := @as(uint, 0) if (*str & 0x80) == 0 { if *str == 10 { cursor.x = pos.x cursor.y += next_line_y str += 1 continue } if font.unicode == null { if *str > font.num_glyphs { str += 1 continue } glyph_data = @inline(get_glyph, font, *str) } else { if *str < UNC_TABLE_SIZE { glyph_index := *(font.unicode + *str) if glyph_index == 0xFFFF { str += 1 continue } glyph_data = font.data + glyph_index * font.bytes_per_glyph } else { str += 1 continue } } str += 1 } else if font.unicode != null { first_byte := *str num_bytes := @as(uint, 0) num_bytes = utf8_len_table[first_byte >> 5 & 0x3] if num_bytes == 0 { str += 1 continue } code_point = first_byte & (0x7F >> num_bytes | 0x1F) valid_sequence := true bytes_processed := 1 loop if bytes_processed >= num_bytes break else { str += 1 if *str == 0 | (*str & 0xC0) != 0x80 { valid_sequence = false } if valid_sequence == false { break } code_point = code_point << 6 | *str & 0x3F bytes_processed += 1 } if valid_sequence == false { str += 1 continue } str += 1 if code_point == 10 { cursor.x = pos.x cursor.y += next_line_y continue } if code_point >= UNC_TABLE_SIZE { continue } glyph_index := *(font.unicode + code_point) if glyph_index == 0xFFFF { continue } glyph_data = font.data + glyph_index * font.bytes_per_glyph } if cursor.x + font.width >= surface_width { cursor.x = pos.x cursor.y += next_line_y } dest := @inline(indexptr, surface, cursor.x, cursor.y) rows := font.height loop if rows == 0 break else { byte := *glyph_data pixel_dest := dest mask := @as(u8, 0x80) bits := font.width loop if bits == 0 break else { if (byte & mask) != 0 { *pixel_dest = color } pixel_dest += 1 mask >>= 1 if mask == 0 & bits > 0 { glyph_data += 1 byte = *glyph_data mask = 0x80 } bits -= 1 } if mask != 0x80 { glyph_data += 1 } dest += surface_width rows -= 1 } cursor.x += char_advance } return }