diff --git a/Common/bits.h b/Common/bits.h new file mode 100644 index 0000000..152dc2c --- /dev/null +++ b/Common/bits.h @@ -0,0 +1,30 @@ +#ifndef COM_BITS_H +#define COM_BITS_H + +#include "def.h" +#include +#include + +/* How many bits needed to represent a given number */ +/* Alternitive semntic: nearest upper power of two */ +static inline int com_bits_needed(uint32_t v) { +#ifdef COM_DEF_COMPILE_MODERN + /* Use intrinsic, required for fast sqrt impl */ + return v == 0 ? 1 : 32 - __builtin_clz(v); +#else +#endif + if (v == 0) + return 1; + + v--; + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + v++; + + return v; +} + +#endif diff --git a/Common/def.h b/Common/def.h new file mode 100644 index 0000000..d3fd5c1 --- /dev/null +++ b/Common/def.h @@ -0,0 +1,19 @@ +/* + Compiler definitions helpers, for portability. +*/ + +#ifndef COM_DEF_H +#define COM_DEF_H + +#if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L) || \ + defined(__GNUC__) || defined(__clang__) +#define COM_DEF_COMPILE_MODERN 1 +#endif + +#ifdef COM_DEF_COMPILE_MODERN +#define com_def_alignedas(v_as) _Alignas(v_as) +#else +#define com_def_alignedas(v_as) +#endif + +#endif diff --git a/Common/fixed.c b/Common/fixed.c new file mode 100644 index 0000000..315c383 --- /dev/null +++ b/Common/fixed.c @@ -0,0 +1,71 @@ +#include "fixed.h" +#include "bits.h" +#include +#include +#include + +// 4-bit LUT (16 entries) for the normalized range [0.5, 2.0) +// It stores the initial guess scaled to Q16.16. +static const uint32_t com_fixed_sqrt_lut[16] = { + 46340, 49547, 52521, 55314, 57954, 60464, 62862, 65161, + 67373, 69508, 71572, 73572, 75514, 77402, 79240, 81033}; + +com_fixed_t com_fixed_sqrt(com_fixed_t a) { + // 1. Handle sign and edge cases + assert(a >= 0); + if (a == 0) + return 0; + + // 2. Normalize input to the range [0.5, 2.0) to maximize LUT precision + // clz = count leading zeros. On modern hardware, use __builtin_clz + int leading_zeros = com_bits_needed(a); + + // Calculate how much we need to shift to place the highest bit properly + // We want the value to land squarely within an optimal window + int shift = (31 - leading_zeros) - COM_FIXED_FRACBITS; + + // Normalize shift to always be even so we can cleanly pull out 2^(shift/2) + if (shift & 1) + shift -= 1; + + uint32_t normalized_a; + if (shift > 0) { + normalized_a = a >> shift; + } else { + normalized_a = a << (-shift); + } + + // 3. LUT Lookup using 4 MSBs of the normalized value + // Extracted index corresponds to the interval [0.5, 2.0) + uint32_t lut_index = (normalized_a >> (COM_FIXED_FRACBITS - 3)) & 0xF; + uint64_t x = com_fixed_sqrt_lut[lut_index]; + + // 4. Newton-Raphson Iterations: x = 0.5 * (x + normalized_a / x) + // We upscale to 64-bit to prevent intermediate overflow during division + x = (x + ((uint64_t)normalized_a << COM_FIXED_FRACBITS) / x) >> + 1; // Iteration 1 + x = (x + ((uint64_t)normalized_a << COM_FIXED_FRACBITS) / x) >> + 1; // Iteration 2 + + // 5. Denormalize back to the target scale: result = x * 2^(shift / 2) + int32_t final_shift = shift / 2; + if (final_shift > 0) { + return (int32_t)(x << final_shift); + } else { + return (int32_t)(x >> (-final_shift)); + } +} + +void com_fixed_print(com_fixed_t a) { + if (a < 0) { + printf("-"); + a = -a; + } + int32_t int_part = a >> 16; + int32_t frac_part = a & 0xFFFF; + + // Convert fractional 16-bit part to a decimal value (up to 4 decimal places) + uint32_t decimal_val = (frac_part * 10000) >> 16; + + printf("%d.%04u\n", int_part, decimal_val); +} diff --git a/Common/fixed.h b/Common/fixed.h new file mode 100644 index 0000000..6eb6c27 --- /dev/null +++ b/Common/fixed.h @@ -0,0 +1,38 @@ +/* + Doom-inspired Q15.16 format used for most of everything. + https://hackmd.io/9uRB9YbTSBW4Qz_2b8TyDw#Examples-2-DOOM +*/ + +#ifndef COM_FIXED_H +#define COM_FIXED_H + +#include + +#define COM_FIXED_FRACBITS 16 +#define COM_FIXED_FRACUNIT (1 << COM_FIXED_FRACBITS) + +typedef int32_t com_fixed_t; + +static inline com_fixed_t com_fixed_add(com_fixed_t a, com_fixed_t b) { + return a + b; +} + +static inline com_fixed_t com_fixed_sub(com_fixed_t a, com_fixed_t b) { + return a - b; +} + +static inline com_fixed_t com_fixed_mul(com_fixed_t a, com_fixed_t b) { + return (com_fixed_t)(((int64_t)a * (int64_t)b) >> COM_FIXED_FRACBITS); +} + +/* Note: this does not clamp for over/underflow cases */ +static inline com_fixed_t com_fixed_div(com_fixed_t a, com_fixed_t b) { + return (com_fixed_t)(((int64_t)a << COM_FIXED_FRACBITS) / (int64_t)b); +} + +/* Approximate square root using Newton-Raphson in 2 iterations over small LUT*/ +com_fixed_t com_fixed_sqrt(com_fixed_t a); + +void com_fixed_print(com_fixed_t a); + +#endif diff --git a/Common/lzw.c b/Common/lzw.c new file mode 100644 index 0000000..25cb062 --- /dev/null +++ b/Common/lzw.c @@ -0,0 +1,154 @@ +/* + LZW compression that is required for GIF implementation. + https://en.wikipedia.org/wiki/Lempel%E2%80%93Ziv%E2%80%93Welch +*/ + +#include "lzw.h" +#include "bits.h" +#include +#include +#include +#include +#include +#include + +/* References: */ +/* https://www.w3.org/Graphics/GIF/spec-gif89a.txt */ +/* https://www.daubnet.com/en/file-format-gif */ + +/* After it is reached the table is supposed to be reset */ +/* As this is known, the upper boundary of memory used is known */ +#define COM_LZW_CODEPOINT_LIMIT 8192 +#define COM_TABLE_BIT_WIDTH_LIMIT 12 /* typical GIF impl */ + +#define COM_LZW_OUTPUT_CAP_GROWTH 256 +#define COM_LZW_TABLE_CAP_GROW 128 + +struct com_lzw_table com_lzw_infer_table(const char *datain, uint32_t sizein) { + struct com_lzw_table_entry *table = NULL; + uint32_t table_init_size = 0; + uint32_t table_size = 0; + uint32_t table_cap = 0; + + /* Infer initial table from the data */ + for (uint32_t i = 0; i < sizein && table_init_size < 255; i++) { + bool code_found = false; + char code = datain[i]; + /* Try searching the code */ + for (uint32_t t = 0; t < table_init_size; t++) { + if (table[t].code == code) { + code_found = true; + break; + } + } + + /* Append non-existing codepoint */ + if (!code_found) { + if (table_size >= table_cap) { + /* TODO: Ref to prev table gets missed, memory leak scenario */ + if (!(table = realloc(table, sizeof(struct com_lzw_table_entry) * + (table_cap + COM_LZW_TABLE_CAP_GROW)))) + goto ERR_ALLOC_INIT_TABLE; + + table_cap += COM_LZW_TABLE_CAP_GROW; + } + + table[table_size] = (struct com_lzw_table_entry){.code = code}; + table_init_size++; + table_size++; + } + } + + return (struct com_lzw_table){ + .table = table, + .cap = table_cap, + .size = table_size, + .init_size = table_init_size, + .continuous = false, + }; + +ERR_ALLOC_INIT_TABLE: + if (table_cap > 0) + free(table); + return (struct com_lzw_table){0}; +} + +void com_lzw_free_table(struct com_lzw_table *table) { + if (!table->table) + return; + free(table->table); + table->size = 0; + table->cap = 0; + table->init_size = 0; +} + +/* For GIFs, color should already be collapsed to indices at this point */ +bool com_lzw_compress(const struct com_lzw_table *table, const char *datain, + uint32_t sizein, char **dataout, uint32_t *sizeout) { + assert(datain && dataout && sizeout && sizein > 0); + assert(*dataout == NULL && *sizeout == 0); + + /* TODO: Make sure the table is cleared */ + + /* Encode step */ + uint8_t codesize = com_bits_needed(table->init_size) + 1; + assert(codesize < COM_TABLE_BIT_WIDTH_LIMIT); + + uint32_t cur_table_idx = + 0; /* Head table in which vector we should be looking into */ + uint32_t feedback = + 0; /* How many bytes feeded from datain to the current string */ + + char *output = NULL; + uint32_t output_size = 0; + uint32_t output_cap = 0; + uint8_t output_bitshift = 0; + + for (uint32_t i = 0; i < sizein; i++) { + char code = datain[i]; + if (feedback == 0) { + /* Should not be possible not to find the first code */ + for (uint32_t t = 0; t < table->init_size; t++) { + if (table->table[t].code == code) { + cur_table_idx = t; + feedback++; + } + } + + } else { + // for (uint32_t v = 0; v < table->table[cur_table_idx].tree_vector_size; + // v++) { + // if (table->table[table->table[cur_table_idx].tree_vector[v]].code == + // code) { + // cur_table_idx = table->table[cur_table_idx].tree_vector[v]; + // feedback++; + // continue; + // } + // } + + /* Emit output, drop the string */ + assert(cur_table_idx < (1 << codesize)); + assert(cur_table_idx < table->size); + + if (output_size == output_cap && output_bitshift + codesize > 8) { + /* TODO: catch alloc failure */ + output = realloc(output, output_cap + COM_LZW_OUTPUT_CAP_GROWTH); + output_cap += COM_LZW_OUTPUT_CAP_GROWTH; + } + + // uint16_t code = cur_table_idx; + // uint8_t bits_to_write = codesize; + // while (bits_to_write > 0) { + // bits_to_write -= + // } + + /* Return to prev char as it wasn't processed */ + feedback = 0; + i--; + } + } + + *sizeout = output_size; + *dataout = output; + return true; +} diff --git a/Common/lzw.h b/Common/lzw.h new file mode 100644 index 0000000..d653c95 --- /dev/null +++ b/Common/lzw.h @@ -0,0 +1,55 @@ +#ifndef COM_LZW_H +#define COM_LZW_H + +#include +#include + +/* Alphabet table for use in LZW algorithms */ +struct com_lzw_table { + /* table_size indexed entry is reserved for clear code */ + /* table_size+1 indexed entry is reserved for terminator */ + struct com_lzw_table_entry *table; + uint32_t init_size; /* Alphabet size, table clear is done over it */ + uint32_t size; + uint32_t cap; + bool continuous; /* If true, first order search is simple indexing, where + codepoint = index, up to table_init_size */ +}; + +struct com_lzw_table_entry { + uint16_t child_chain; /* Start of linked list inside table, or 0 if none */ + uint16_t next_child; /* Index of next child, or 0 if none */ + char code; /* Single byte, children bytes are appended to it */ +}; + +/* Returns table that is optimized for continuous range of codepoints */ +/* Useful for binary compression, such as .GIF and .SAM formats */ +struct com_lzw_table com_lzw_continous_table(uint8_t codepoints); + +/* Returns table with alphabet inferred from the incoming data */ +/* Useful for alphanumeric compression, where not all codepoints are in use */ +/* TODO: Sort table by frequency? */ +struct com_lzw_table com_lzw_infer_table(const char *datain, uint32_t sizein); + +/* Each bit corresponds to a byte value, position-wise */ +struct com_lzw_encoded_alphabet { + uint64_t b0; + uint64_t b1; + uint64_t b2; + uint64_t b3; +}; +struct com_lzw_encoded_alphabet +com_lzw_encode_alphabet(const struct com_lzw_table *table); + +struct com_lzw_table +com_lzw_decode_table(struct com_lzw_encoded_alphabet alphabet); + +/* Return table to its original form */ +void com_lzw_clear_table(struct com_lzw_table *table); + +void com_lzw_free_table(struct com_lzw_table *table); + +bool com_lzw_compress(const struct com_lzw_table *table, const char *datain, + uint32_t sizein, char **dataout, uint32_t *sizeout); + +#endif diff --git a/Common/mat.h b/Common/mat.h new file mode 100644 index 0000000..33fd952 --- /dev/null +++ b/Common/mat.h @@ -0,0 +1,137 @@ +/* + Fixed point implementation of 4 dimensional matrix. + Some optimizing cases are present, such as reordered matrices and assumed + identity components. + + Column-major ordering is assumed unless stated otherwise: + + a e k o + b f l p + c g m q + d h n r + + In memory: a b c d e f g h ... +*/ + +#ifndef COM_MAT_H +#define COM_MAT_H + +#include "fixed.h" +#include "vec.h" +#include +#include + +typedef union { + com_fixed_t com_def_alignedas(64) a[4 * 4]; +} com_mat_t; + +static inline com_mat_t com_mat_identity(void) { + com_mat_t result = {0}; + + result.a[0 * 4 + 0] = COM_FIXED_FRACUNIT; + result.a[1 * 4 + 1] = COM_FIXED_FRACUNIT; + result.a[2 * 4 + 2] = COM_FIXED_FRACUNIT; + result.a[3 * 4 + 3] = COM_FIXED_FRACUNIT; + + return result; +} + +/* https://michalpitr.substack.com/p/optimizing-matrix-multiplication */ +static inline com_mat_t com_mat_mul(com_mat_t a, com_mat_t b) { + // com_mat_t result = {0}; + + // for (int c = 0; c < 4; ++c) { + // for (int k = 0; k < 4; ++k) { + // for (int r = 0; r < 4; ++r) { + // result.a[r + c * 4] += com_fixed_mul(a.a[r + k * 4], b.a[k + c * 4]); + // } + // } + // } + + com_mat_t result; + + for (int c = 0; c < 4; ++c) { + for (int r = 0; r < 4; ++r) { + result.a[r + c * 4] = (((int64_t)a.a[r + 0 * 4] * b.a[0 + c * 4]) + + ((int64_t)a.a[r + 1 * 4] * b.a[1 + c * 4]) + + ((int64_t)a.a[r + 2 * 4] * b.a[2 + c * 4]) + + ((int64_t)a.a[r + 3 * 4] * b.a[3 + c * 4])) >> + COM_FIXED_FRACBITS; + } + } + + return result; +} + +/* This case might be slightly more optimized, as we can reorder one frequently + * reused matrix, such as VP. + */ +/* Note: second a matrix is assumed to be row-major, reverse of typical. */ +static inline com_mat_t com_mat_mul_reodered(com_mat_t a, com_mat_t b) { + com_mat_t result; + + for (int c = 0; c < 4; ++c) { + for (int r = 0; r < 4; ++r) { + result.a[r + c * 4] = (((int64_t)a.a[0 + r * 4] * b.a[0 + c * 4]) + + ((int64_t)a.a[1 + r * 4] * b.a[1 + c * 4]) + + ((int64_t)a.a[2 + r * 4] * b.a[2 + c * 4]) + + ((int64_t)a.a[3 + r * 4] * b.a[3 + c * 4])) >> + COM_FIXED_FRACBITS; + } + } + + return result; +} + +/* Projects vertex position to a screen via reordered row-major MVP matrix, + * which implies division by w in-place */ +static inline com_vec_t com_mat_vec_project(com_mat_t a, com_vec_t b) { + com_fixed_t t[4]; + com_vec_t result; + + for (int c = 0; c < 4; ++c) { + t[c] = + (((int64_t)a.a[c * 4 + 0] * b.s.x) + ((int64_t)a.a[c * 4 + 1] * b.s.y) + + ((int64_t)a.a[c * 4 + 2] * b.s.z) + a.a[c * 4 + 3]) >> + COM_FIXED_FRACBITS; + } + + /* Creates perspective effect, could be skipped for orthographic */ + result.a[0] = com_fixed_div(t[0], t[3]); + result.a[1] = com_fixed_div(t[1], t[3]); + result.a[2] = com_fixed_div(t[2], t[3]); + + return result; +} + +/* Slightly optimized case of assumed identity scaling, might be useful for MVP + * calculations, if model matrix does not scale. View matrix is always + * unscaled as well. + */ +// static inline com_mat_t com_mat_mul_no_scale(com_mat_t a, com_mat_t b) { +// com_mat_t result; + +// /* TODO: calc the rest */ + +// result.a[0 * 4 + 3] = 0; +// result.a[1 * 4 + 3] = 0; +// result.a[2 * 4 + 3] = 0; +// result.a[3 * 4 + 3] = COM_FIXED_FRACUNIT; + +// return result; +// } + +/* Reorder between column and row major, it's also called transposing */ +static inline com_mat_t com_mat_reoder(com_mat_t a) { + com_mat_t result; + + for (int r = 0; r < 4; ++r) { + for (int c = 0; c < 4; ++c) { + result.a[c * 4 + r] = a.a[c + r * 4]; + } + } + + return result; +} + +#endif diff --git a/Common/vec.h b/Common/vec.h new file mode 100644 index 0000000..5a9c906 --- /dev/null +++ b/Common/vec.h @@ -0,0 +1,80 @@ +/* + Fixed point implementation of 3 dimensional vectors. + SSE2 extension availability is assumed, making it more viable. + For mul and div we don't use com_fixed functions to make fewer bitshifts. +*/ + +#ifndef COM_VEC_H +#define COM_VEC_H + +#include "def.h" +#include "fixed.h" +#include + +typedef union { + com_fixed_t com_def_alignedas(16) a[3]; + com_def_alignedas(16) struct { + com_fixed_t x; + com_fixed_t y; + com_fixed_t z; + } s; +} com_vec_t; + +static inline com_vec_t com_vec_add(com_vec_t a, com_vec_t b) { + return (com_vec_t){.s = {.x = com_fixed_add(a.s.x, b.s.x), + .y = com_fixed_add(a.s.y, b.s.y), + .z = com_fixed_add(a.s.z, b.s.z)}}; +} + +static inline com_vec_t com_vec_sub(com_vec_t a, com_vec_t b) { + return (com_vec_t){.s = {.x = com_fixed_sub(a.s.x, b.s.x), + .y = com_fixed_sub(a.s.y, b.s.y), + .z = com_fixed_sub(a.s.z, b.s.z)}}; +} + +static inline com_vec_t com_vec_mul(com_vec_t a, com_vec_t b) { + return (com_vec_t){.s = {.x = com_fixed_mul(a.s.x, b.s.x), + .y = com_fixed_mul(a.s.y, b.s.y), + .z = com_fixed_mul(a.s.z, b.s.z)}}; +} + +/* Note: this does not clamp for over/underflow cases */ +static inline com_vec_t com_vec_div(com_vec_t a, com_vec_t b) { + return (com_vec_t){.s = {.x = com_fixed_div(a.s.x, b.s.x), + .y = com_fixed_div(a.s.y, b.s.y), + .z = com_fixed_div(a.s.z, b.s.z)}}; +} + +/* Scale vector by a fixed point number */ +static inline com_vec_t com_vec_scl(com_vec_t a, com_fixed_t b) { + return (com_vec_t){.s = {.x = com_fixed_mul(a.s.x, b), + .y = com_fixed_mul(a.s.y, b), + .z = com_fixed_mul(a.s.z, b)}}; +} + +/* Shows how much given vectors are correlated in direction to each other */ +/* Resulted range depends on input, it's in -1 to 1 for normalized + * input and otherwise is -ab to +ab */ +static inline com_fixed_t com_vec_dot(com_vec_t a, com_vec_t b) { + return (((int64_t)a.s.x * b.s.x) + ((int64_t)a.s.y * b.s.y) + + ((int64_t)a.s.z * b.s.z)) >> + COM_FIXED_FRACBITS; +} + +/* Cross product produces a perpendicular for normalized vectors, or 0 for + * parallel vectors */ +static inline com_vec_t com_vec_crs(com_vec_t a, com_vec_t b) { + int64_t const cx = ((int64_t)a.s.y * b.s.z) - ((int64_t)a.s.z - b.s.y); + int64_t const cy = ((int64_t)a.s.z * b.s.x) - ((int64_t)a.s.x - b.s.z); + int64_t const cz = ((int64_t)a.s.x * b.s.y) - ((int64_t)a.s.y - b.s.x); + return (com_vec_t){.s = {.x = (com_fixed_t)(cx >> COM_FIXED_FRACBITS), + .y = (com_fixed_t)(cy >> COM_FIXED_FRACBITS), + .z = (com_fixed_t)(cz >> COM_FIXED_FRACBITS)}}; +} + +static inline com_vec_t com_vec_nrm(com_vec_t a) { + com_fixed_t const n = com_fixed_sqrt(com_vec_dot(a, a)); + return com_vec_scl(a, com_fixed_div(COM_FIXED_FRACUNIT, n)); +} + +#endif diff --git a/Player/Display/display.h b/Player/Display/display.h index a805693..6af90a3 100644 --- a/Player/Display/display.h +++ b/Player/Display/display.h @@ -1,2 +1,7 @@ +#ifndef PLR_DISPLAY_H +#define PLR_DISPLAY_H extern int plr_display_x11_main(int argc, char *argv[]); +extern int plr_display_dos_main(int argc, char *argv[]); + +#endif diff --git a/Player/Display/dos.c b/Player/Display/dos.c new file mode 100644 index 0000000..812c35d --- /dev/null +++ b/Player/Display/dos.c @@ -0,0 +1,152 @@ +#include "display.h" +#include +#include + +#define WIDTH 640 +#define HEIGHT 480 +#define MODE_VGA_320_200_8 0x13 +#define MODE_VESA_640_480_8 0x101 + +// 8-color VGA palette gradient, lighter shades of lavender to pink +#define COLOR_OFFSET 0x50 +#define COLOR_NUM 8 +#define COLOR_STEP (HEIGHT / COLOR_NUM) + +/* https://wiki.osdev.org/VESA_Video_Modes */ +_Packed struct VbeInfoBlock { + char VbeSignature[4]; // == "VESA" + uint16_t VbeVersion; // == 0x0300 for VBE 3.0 + uint16_t OemStringPtr[2]; // isa vbeFarPtr + uint8_t Capabilities[4]; + uint16_t VideoModePtr[2]; // isa vbeFarPtr + uint16_t TotalMemory; // as # of 64KB blocks + uint8_t Reserved[492]; +}; + +_Packed struct ModeInfoBlock { + uint16_t + attributes; // deprecated, only bit 7 should be of interest to you, and it + // indicates the mode supports a linear frame buffer. + uint8_t window_a; // deprecated + uint8_t window_b; // deprecated + uint16_t granularity; // deprecated; used while calculating bank numbers + uint16_t window_size; + uint16_t segment_a; + uint16_t segment_b; + uint32_t win_func_ptr; // deprecated; used to switch banks from protected mode + // without returning to real mode + uint16_t pitch; // number of bytes per horizontal line + uint16_t width; // width in pixels + uint16_t height; // height in pixels + uint8_t w_char; // unused... + uint8_t y_char; // ... + uint8_t planes; + uint8_t bpp; // bits per pixel in this mode + uint8_t banks; // deprecated; total number of banks in this mode + uint8_t memory_model; + uint8_t bank_size; // deprecated; size of a bank, almost always 64 KB but may + // be 16 KB... + uint8_t image_pages; + uint8_t reserved0; + + uint8_t red_mask; + uint8_t red_position; + uint8_t green_mask; + uint8_t green_position; + uint8_t blue_mask; + uint8_t blue_position; + uint8_t reserved_mask; + uint8_t reserved_position; + uint8_t direct_color_attributes; + + uint32_t framebuffer; // physical address of the linear frame buffer; write + // here to draw to the screen + uint32_t off_screen_mem_off; + uint16_t off_screen_mem_size; // size of memory in the framebuffer but not + // being displayed on the screen + uint8_t reserved1[206]; +}; + +static char get_video_mode(); +#pragma aux get_video_mode = "mov ah, 0x0f" \ + "int 0x10" value[al] modify[ah]; + +static void set_video_mode(unsigned char); +#pragma aux set_video_mode = "mov ah, 0x00" \ + "int 0x10" parm[al] modify[ah]; + +static void set_vesa_video_mode(unsigned short); +#pragma aux set_vesa_video_mode = "mov ax, 0x4f02" \ + "int 0x10" parm[bx] modify[ah]; + +static void wait_for_key(); +#pragma aux wait_for_key = "mov ah, 0x00" \ + "int 0x16" modify[ah]; + +static short get_vesa_controller_info(struct VbeInfoBlock *); +#pragma aux get_vesa_controller_info = "mov ax, 0x4f00" \ + "int 0x10" parm[es di] value[ax]; + +static short get_vesa_mode_info(uint16_t mode, struct ModeInfoBlock *); +#pragma aux get_vesa_mode_info = "mov ax, 0x4F01" \ + "int 0x10" parm[cx][es di] value[ax]; + +/* https://wiki.osdev.org/User:Omarrx024/VESA_Tutorial*/ +static char *find_display_memory(void) { + struct VbeInfoBlock *ctrl = (struct VbeInfoBlock *)0x2000; + struct ModeInfoBlock *inf = (struct ModeInfoBlock *)0x3000; + uint16_t *modes; + int i; + + strncpy(ctrl->VbeSignature, "VBE2", 4); + if (get_vesa_controller_info(ctrl) != 0x004F) + return NULL; + + modes = (uint16_t *)(ctrl->VideoModePtr); + for (i = 0; modes[i] != 0xFFFF; ++i) { + if (get_vesa_mode_info(modes[i], inf) != 0x004F) + continue; + + // Check if this is a graphics mode with linear frame buffer support + if ((inf->attributes & 0x80) != 0x80) + continue; + + // Check if this is a packed pixel or direct color mode + if (inf->memory_model != 4 && inf->memory_model != 6) + continue; + + // Check if this is exactly the mode we're looking for + if (WIDTH == inf->width && HEIGHT == inf->height && 256 == inf->pitch) + return (char *)inf->framebuffer; + } + + return NULL; +} + +extern int plr_display_dos_main(int argc, char *argv[]) { + char far *buf = (char far *)0xA0000; // VGA memory address + int x, y; + + unsigned char saved_mode = get_video_mode(); // Save original mode + set_vesa_video_mode(MODE_VESA_640_480_8); // Set our VGA mode + + buf = find_display_memory(); + + /* TODO: Fetch info about the mode, to know the availability */ + /* as well as base address in memory */ + /* https://wiki.osdev.org/VESA_Video_Modes */ + + // Now draw. We draw pixel-by-pixel by directly setting the video memory like + // it was an array. We step the color every 25 pixels so that we have 8 bands + // of even height. + for (y = 0; y < HEIGHT; y++) { + for (x = 0; x < WIDTH; x++) { + buf[y * WIDTH + x] = COLOR_OFFSET + y / COLOR_STEP; + } + } + + wait_for_key(); + set_video_mode(saved_mode); // Restore original mode + + return 0; +} diff --git a/Player/Display/x11.c b/Player/Display/x11.c index 10db5d5..ab19606 100644 --- a/Player/Display/x11.c +++ b/Player/Display/x11.c @@ -1,120 +1,144 @@ -#include -#include -#include #include #include #include +#include +#include +#include +#include +#include + +#include "../../Common/lzw.h" +#include "../../Common/mat.h" #include "display.h" bool quited = false; -#define WIDTH 640 +#define WIDTH 640 #define HEIGHT 480 // TODO: correct error handling. -extern int plr_display_x11_main(int argc, char *argv[]) -{ - Display *display = XOpenDisplay(NULL); - if (NULL == display) { - fprintf(stderr, "Failed to initialize display"); - return EXIT_FAILURE; - } +extern int plr_display_x11_main(int argc, char *argv[]) { + char test_string[] = "what is this?"; + char *compressed_string = NULL; + uint32_t compressed_string_sz = 0; + struct com_lzw_table table = + com_lzw_infer_table(test_string, sizeof(test_string)); + bool test = com_lzw_compress(&table, test_string, sizeof(test_string), + &compressed_string, &compressed_string_sz); + com_lzw_free_table(&table); + assert(test); - Window root = DefaultRootWindow(display); - if (None == root) { - fprintf(stderr, "No root window found"); - XCloseDisplay(display); - return EXIT_FAILURE; - } + com_mat_t m0 = com_mat_identity(); + com_mat_t m1 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; + for (int i = 0; i < 16; ++i) + m1.a[i] *= COM_FIXED_FRACUNIT; - int screen = DefaultScreen(display); - Visual *visual = DefaultVisual(display, screen); - int depth = DefaultDepth(display, screen); + com_mat_t t0 = com_mat_mul(m1, m0); + com_mat_t t1 = com_mat_mul_reodered(com_mat_reoder(m1), m0); + com_fixed_print(t0.a[4]); + com_fixed_print(t1.a[4]); - Window window = XCreateSimpleWindow(display, root, 0, 0, WIDTH, HEIGHT, 0, 0, 0xffffffff); - if (None == window) { - fprintf(stderr, "Failed to create window"); - XCloseDisplay(display); - return EXIT_FAILURE; - } - - XSizeHints *hints = XAllocSizeHints(); - if (hints == NULL) return EXIT_FAILURE; - - // Pinning min and max to the same values disables resizing - hints->flags = PMinSize | PMaxSize; - hints->min_width = hints->max_width = WIDTH; - hints->min_height = hints->max_height = HEIGHT; - - XSetWMNormalHints(display, window, hints); - XFree(hints); - - XSelectInput(display, window, ExposureMask | KeyPressMask); - XMapWindow(display, window); - - GC gc = XCreateGC(display, window, 0, NULL); - - Atom wm_delete_window = XInternAtom(display, "WM_DELETE_WINDOW", False); - XSetWMProtocols(display, window, & wm_delete_window, 1); - - // TODO: can't be not true - int bytes_per_pixel = 4; - char *pixel_buffer = (char *)malloc(WIDTH * HEIGHT * bytes_per_pixel); - - XImage *ximage = XCreateImage( - display, visual, depth, ZPixmap, 0, - pixel_buffer, WIDTH, HEIGHT, 32, 0 - ); - - if (!ximage) { - fprintf(stderr, "Failed to create XImage\n"); - free(pixel_buffer); - XCloseDisplay(display); - return EXIT_FAILURE; - } - - int frame = 0; - - XEvent event; - while (!quited) { - XNextEvent(display, &event); - - switch(event.type) { - case ClientMessage: - if(event.xclient.data.l[0] == wm_delete_window) { - XDestroyWindow(display, window); - quited = true; - } - break; - case Expose: - frame += 2; - // Fill buffer with a simple color gradient - for (int y = 0; y < HEIGHT; y++) { - for (int x = 0; x < WIDTH; x++) { - int pixel_index = (y * WIDTH + x) * bytes_per_pixel; - - // Assuming standard Little-Endian 32-bit BGRA format - pixel_buffer[pixel_index + 0] = (x + frame) % 256; // Blue - pixel_buffer[pixel_index + 1] = (y + frame) % 256; // Green - pixel_buffer[pixel_index + 2] = (x + y - frame) % 256; // Red - pixel_buffer[pixel_index + 3] = 0; // Alpha/Padding - } - } - // Draw the complete image onto the window when exposed - XPutImage( - display, - window, // Target drawable - gc, // Graphics Context - ximage, // Source XImage - 0, 0, // Source coordinates (x, y) - 0, 0, // Destination coordinates (x, y) - WIDTH, HEIGHT// Dimensions to copy - ); - break; - } - } + Display *display = XOpenDisplay(NULL); + if (NULL == display) { + fprintf(stderr, "Failed to initialize display"); + return EXIT_FAILURE; + } + Window root = DefaultRootWindow(display); + if (None == root) { + fprintf(stderr, "No root window found"); XCloseDisplay(display); + return EXIT_FAILURE; + } - return 0; + int screen = DefaultScreen(display); + Visual *visual = DefaultVisual(display, screen); + int depth = DefaultDepth(display, screen); + + Window window = + XCreateSimpleWindow(display, root, 0, 0, WIDTH, HEIGHT, 0, 0, 0xffffffff); + if (None == window) { + fprintf(stderr, "Failed to create window"); + XCloseDisplay(display); + return EXIT_FAILURE; + } + + XSizeHints *hints = XAllocSizeHints(); + if (hints == NULL) + return EXIT_FAILURE; + + // Pinning min and max to the same values disables resizing + hints->flags = PMinSize | PMaxSize; + hints->min_width = hints->max_width = WIDTH; + hints->min_height = hints->max_height = HEIGHT; + + XSetWMNormalHints(display, window, hints); + XFree(hints); + + XSelectInput(display, window, ExposureMask | KeyPressMask); + XMapWindow(display, window); + + GC gc = XCreateGC(display, window, 0, NULL); + + Atom wm_delete_window = XInternAtom(display, "WM_DELETE_WINDOW", False); + XSetWMProtocols(display, window, &wm_delete_window, 1); + + // TODO: can't be not true + int bytes_per_pixel = 4; + char *pixel_buffer = (char *)malloc(WIDTH * HEIGHT * bytes_per_pixel); + + XImage *ximage = XCreateImage(display, visual, depth, ZPixmap, 0, + pixel_buffer, WIDTH, HEIGHT, 32, 0); + + if (!ximage) { + fprintf(stderr, "Failed to create XImage\n"); + free(pixel_buffer); + XCloseDisplay(display); + return EXIT_FAILURE; + } + + int frame = 0; + + XEvent event; + while (!quited) { + XNextEvent(display, &event); + + switch (event.type) { + case ClientMessage: + if (event.xclient.data.l[0] == wm_delete_window) { + XDestroyWindow(display, window); + quited = true; + } + break; + + case Expose: + frame += 2; + // Fill buffer with a simple color gradient + for (int y = 0; y < HEIGHT; y++) { + for (int x = 0; x < WIDTH; x++) { + int pixel_index = (y * WIDTH + x) * bytes_per_pixel; + + // Assuming standard Little-Endian 32-bit BGRA format + pixel_buffer[pixel_index + 0] = (x + frame) % 256; // Blue + pixel_buffer[pixel_index + 1] = (y + frame) % 256; // Green + pixel_buffer[pixel_index + 2] = (x + y - frame) % 256; // Red + pixel_buffer[pixel_index + 3] = 0; // Alpha/Padding + } + } + // Draw the complete image onto the window when exposed + XPutImage(display, + window, // Target drawable + gc, // Graphics Context + ximage, // Source XImage + 0, 0, // Source coordinates (x, y) + 0, 0, // Destination coordinates (x, y) + WIDTH, HEIGHT // Dimensions to copy + ); + break; + } + } + + XCloseDisplay(display); + + return 0; } diff --git a/Player/Makefile b/Player/Makefile index 2ef300f..4c83639 100644 --- a/Player/Makefile +++ b/Player/Makefile @@ -1,10 +1,18 @@ CC=clang -CFLAGS=-lX11 -DEPS = -OBJ = main.o Display/x11.o +CFLAGS=-lX11 -Wall -std=c99 -g3 -O3 -fno-inline -msse2 +DEPS = Display/display.h ../Common/lzw.h ../Common/mat.h ../Common/vec.h ../Common/fixed.h ../Common/def.h ../Common/bits.h +OBJ = ../Common/lzw.o ../Common/fixed.o +LINUX = main.o Display/x11.o +DOS = maindos.c Display/dos.c %.o: %.c $(DEPS) $(CC) -c -o $@ $< $(CFLAGS) -player: $(OBJ) - $(CC) -o $@ $^ $(CFLAGS) +linux: $(LINUX) $(OBJ) + $(CC) -o player $^ $(CFLAGS) + +dos: $(DOS) $(DEPS) + wcl386 -bt=dos4g -ox -l=dos4g $(DOS) + +clean: + rm -r *.o ../*.o diff --git a/Player/Socket/socket.h b/Player/Socket/socket.h new file mode 100644 index 0000000..e69de29 diff --git a/Player/Socket/unix.c b/Player/Socket/unix.c new file mode 100644 index 0000000..6019422 --- /dev/null +++ b/Player/Socket/unix.c @@ -0,0 +1,26 @@ +/* + Inter-process communicating socket, useful for local testing and admin + playing. +*/ + +#include +#include +#include + +#define MAXBUFFSIZE 4194304 /* 4.0MiB */ + +int plr_socket_unix_try_connect(const char *filepath) { + int sock = 0; + + if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { + perror("Error creating client UNIX socket: "); + return -1; + } + + struct sockaddr_un remote = {0}; + remote.sun_family = AF_UNIX; + strcpy(remote.sun_path, filepath); + int const remote_len = SUN_LEN(&remote); + + return 0; +} diff --git a/Player/docs.txt b/Player/docs.txt index aa75442..1e8e9eb 100644 --- a/Player/docs.txt +++ b/Player/docs.txt @@ -1,13 +1,39 @@ Pustina -- Player -BYOND-inspired optimized web-driven content player. -All logic is processed on the server, whereas player only does display changes and sends unreliable inputs. -This allows for no-update and hidden content strategies, allowing for mystic and truly seamlessly progressing world. +Locked step multiplayer oriented engine with great restrictions. We do not use abstractions, as the simplicity of the required set of features should allow for platform-specific implementations. -Restrictions: --- Fixed sized frambuffer, default is 640x480, but platforms can implement their own. Scaling by 2 is possible. --- GIF image format for all drawing purposes. Delta Frame Optimiztion is encouraged. --- Images are 32x32 or multiples of it, aligning to the grid. --- 20 FPS display rate. --- Fonts are in ASCII bitmaps. --- Audio samples are in vorbis format. Music pieces are written in our own simple tracker format, using the same vorbis samples. + +---- Restrictions ---- +* Fixed sized paletted frambuffer, defaulted to 640x480. Scaling by 2 is possible. +* Deterministic logic via Q15.16 fixed point numbers. +* .gam image format, derived from .gif. RLE and Delta Frame Optimiztion is added, palette is predefined. +* Images are 16x16 or multiples of it, aligning to the grid. +* Tile view is capped at 29x29, the rest is allocated to interface. +* 30 FPS display rate. +* Fonts are in ASCII bitmaps. Tile slicing commands are issued to render them in. +* Audio samples are in our own .sam format (s8 frame delta + lzw). +* Music pieces are written in our own simple .tam tracker format, using the same .sam sample db. +* Connections are over TCP and their supersets (like WebSocket). +* Textbox provides another client-controlled view, with history. +* Content usage has to be predefined, to allow preloading and compilation. + + +---- Building ---- +==== DOS ==== +Open Watcom 2.0 32bit toolchain is used under Linux host. +Place a copy of DOS4GW.EXE alongside the executable. +Expect to need to configure $INCLUDE and $LIB variables. + + +---- .sam format ---- +Thin sample compressing scheme tailored for use with .tam tracker. +Leading and tailing silence is stored as number of frames. +Sample data is 1 channel only, but panning can be expressed via graph. +Sample bit format is signed 8 bit delta, first frame is delta against 0. Clamping is allowed. +Generic LZW compression is applied as the last step. + + +---- .dab format --- +Compressed streamed content database format. +It allows for seeked access of required portions only via the content table. +Content table stores pathing, content and compression metadata as well as sized offset inside the file. diff --git a/Player/main.c b/Player/main.c index d6e9441..0110ad1 100644 --- a/Player/main.c +++ b/Player/main.c @@ -1,5 +1,5 @@ #include "Display/display.h" extern int main(int argc, char *argv[]) { - return plr_display_x11_main(argc, argv); + return plr_display_x11_main(argc, argv); } diff --git a/Player/maindos.c b/Player/maindos.c new file mode 100644 index 0000000..f21c1e5 --- /dev/null +++ b/Player/maindos.c @@ -0,0 +1,5 @@ +#include "Display/display.h" + +extern int main(int argc, char *argv[]) { + return plr_display_dos_main(argc, argv); +}