This commit is contained in:
veclavtlica
2026-09-11 02:21:21 +03:00
parent 2e890fb5b7
commit adfae10938
17 changed files with 949 additions and 119 deletions
+5
View File
@@ -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
+152
View File
@@ -0,0 +1,152 @@
#include "display.h"
#include <stdint.h>
#include <string.h>
#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;
}
+127 -103
View File
@@ -1,120 +1,144 @@
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#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;
}
+13 -5
View File
@@ -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
View File
+26
View File
@@ -0,0 +1,26 @@
/*
Inter-process communicating socket, useful for local testing and admin
playing.
*/
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>
#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;
}
+36 -10
View File
@@ -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.
+1 -1
View File
@@ -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);
}
+5
View File
@@ -0,0 +1,5 @@
#include "Display/display.h"
extern int main(int argc, char *argv[]) {
return plr_display_dos_main(argc, argv);
}