Index: emu/all.h
===================================================================
--- emu/all.h	(revision ac4e192cb8973aecb5752d6cc4436c183b1807cb)
+++ emu/all.h	(revision f28585850a4f27fe3a5996dcd3dce0fee4b42f33)
@@ -97,4 +97,6 @@
 extern void vid_write(uint32_t off, int32_t sz, uint32_t val);
 
+extern void vid_sdl(void);
+
 extern void tim_init(void);
 extern void tim_quit(void);
Index: emu/sdl.c
===================================================================
--- emu/sdl.c	(revision ac4e192cb8973aecb5752d6cc4436c183b1807cb)
+++ emu/sdl.c	(revision f28585850a4f27fe3a5996dcd3dce0fee4b42f33)
@@ -27,5 +27,5 @@
 
 static sdl_func_t sdl_funcs[] = {
-	ser_sdl
+	ser_sdl, vid_sdl
 };
 
Index: emu/vid.c
===================================================================
--- emu/vid.c	(revision ac4e192cb8973aecb5752d6cc4436c183b1807cb)
+++ emu/vid.c	(revision f28585850a4f27fe3a5996dcd3dce0fee4b42f33)
@@ -45,5 +45,7 @@
 #define PAL_OFF 0x40000
 
+#define OD0_TDE 0x0004
 #define OD0_BLA 0x0010
+#define OD0_CB 0x0800
 
 #define WIN_SZ_W 0x10000
@@ -52,4 +54,15 @@
 #define N_BANKS 2
 
+#define SCALE(_x) (_x * 2)
+
+#define WIN_W SCALE(SCR_W)
+#define WIN_H SCALE(SCR_H)
+
+typedef struct {
+	int32_t x, y, w, h;
+	uint16_t *mem;
+	bool tran;
+} bm_obj_t;
+
 static int32_t reg_off = REG_OFF;
 
@@ -57,10 +70,127 @@
 static int32_t bank = 0;
 
-static int32_t pal[16];
-static int32_t i_pal = 0;
+static uint32_t pal[16];
+
+static bm_obj_t bm_objs[16];
+static int32_t n_bm_objs = 0;
+
+static SDL_Window *win;
+static SDL_Renderer *ren;
+static SDL_Texture *tex;
+static SDL_atomic_t frame;
+
+void vid_sdl(void)
+{
+	ver3("vid_sdl()");
+
+	static int32_t last = 0;
+	int32_t now = SDL_AtomicGet(&frame);
+
+	if (last == now) {
+		ver3("no update");
+		return;
+	}
+
+	last = now;
+
+	if (SDL_RenderFillRect(ren, NULL) < 0) {
+		fail("SDL_RenderFillRect() failed: %s", SDL_GetError());
+	}
+
+	void *buf;
+	int32_t pitch;
+
+	if (SDL_LockMutex(cpu_mutex) < 0) {
+		fail("SDL_LockMutex() failed: %s", SDL_GetError());
+	}
+
+	for (int32_t i = 0; i < n_bm_objs; ++i) {
+		bm_obj_t *bm_obj = bm_objs + i;
+
+		pal[0] = bm_obj->tran ? pal[0] & 0xffffff00 : pal[0] | 0x000000ff;
+
+		SDL_Rect src = {
+			.x = 0, .y = 0, .w = bm_obj->w, .h = bm_obj->h
+		};
+
+		if (SDL_LockTexture(tex, &src, &buf, &pitch) < 0) {
+			fail("SDL_LockTexture() failed: %s", SDL_GetError());
+		}
+
+		uint32_t *pix = buf;
+		uint16_t *vid = bm_obj->mem;
+
+		for (int32_t y = 0; y < src.h; ++y) {
+			for (int32_t x = 0; x < src.w / 4; ++x) {
+				uint16_t v4 = *vid++;
+
+				*pix++ = pal[(v4 & 0x000f) >>  0];
+				*pix++ = pal[(v4 & 0x00f0) >>  4];
+				*pix++ = pal[(v4 & 0x0f00) >>  8];
+				*pix++ = pal[(v4 & 0xf000) >> 12];
+			}
+
+			pix += pitch / 4 - src.w;
+		}
+
+		SDL_UnlockTexture(tex);
+
+		SDL_Rect dst = {
+			.x = SCALE(bm_obj->x), .y = SCALE(bm_obj->y),
+			.w = SCALE(bm_obj->w), .h = SCALE(bm_obj->h)
+		};
+
+		ver2("vid rend %d %dx%d -> (%d, %d) (%d, %d) %dx%d",
+				i,
+				bm_obj->w, bm_obj->h,
+				bm_obj->x, bm_obj->y,
+				SCALE(bm_obj->x), SCALE(bm_obj->y),
+				SCALE(bm_obj->w), SCALE(bm_obj->h));
+
+		if (SDL_RenderCopy(ren, tex, &src, &dst) < 0) {
+			fail("SDL_RenderCopy() failed: %s", SDL_GetError());
+		}
+	}
+
+	if (SDL_UnlockMutex(cpu_mutex) < 0) {
+		fail("SDL_UnlockMutex() failed: %s", SDL_GetError());
+	}
+
+	SDL_RenderPresent(ren);
+}
 
 void vid_init(void)
 {
 	ver("vid init");
+
+	win = SDL_CreateWindow("Display", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
+			WIN_W, WIN_H, 0);
+
+	if (win == NULL) {
+		fail("SDL_CreateWindow() failed: %s", SDL_GetError());
+	}
+
+	ren = SDL_CreateRenderer(win, -1, 0);
+
+	if (ren == NULL) {
+		fail("SDL_CreateRenderer() failed: %s", SDL_GetError());
+	}
+
+	if (SDL_SetRenderDrawColor(ren, 0x00, 0x00, 0x00, 0xff) < 0) {
+		fail("SDL_SetRenderDrawColor() failed: %s", SDL_GetError());
+	}
+
+	tex = SDL_CreateTexture(ren, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STREAMING,
+			SCR_W, SCR_H);
+
+	if (tex == NULL) {
+		fail("SDL_CreateTexture() failed: %s", SDL_GetError());
+	}
+
+	if (SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_BLEND) < 0) {
+		fail("SDL_SetTextureBlendMode() failed: %s", SDL_GetError());
+	}
+
+	SDL_AtomicSet(&frame, 1);
 }
 
@@ -68,4 +198,8 @@
 {
 	ver("vid quit");
+
+	SDL_DestroyTexture(tex);
+	SDL_DestroyRenderer(ren);
+	SDL_DestroyWindow(win);
 }
 
@@ -115,14 +249,19 @@
 
 	int32_t odtba = mem[REG_ODTBA] & 0xffc0;
-	int32_t y_beg[16], y_end[16];
+
+	if (SDL_LockMutex(cpu_mutex) < 0) {
+		fail("SDL_LockMutex() failed: %s", SDL_GetError());
+	}
+
+	n_bm_objs = 0;
 
 	for (int32_t i = 0; i < 16; ++i) {
-		y_beg[i] = -1;
-		y_end[i] = -1;
+		int32_t flips[SCR_H];
+		int32_t n_flips = 0;
 
 		uint16_t *od = mem + odtba + 4 * i;
 
 		if ((od[0] & OD0_BLA) != 0) {
-			ver3("obj %d blanked", i);
+			ver3("vid obj %d blanked", i);
 			continue;
 		}
@@ -131,5 +270,5 @@
 
 		if (w == 0) {
-			ver3("obj %d empty", i);
+			ver3("vid obj %d empty", i);
 			continue;
 		}
@@ -139,25 +278,51 @@
 		for (int32_t k = 0; k < SCR_H; ++k) {
 			if ((mem[atba + k] & mask) == 0) {
-				if (y_beg[i] < 0) {
-					y_beg[i] = k;
-				}
-				else if (y_end[i] < 0) {
-					y_end[i] = k;
-				}
+				flips[n_flips] = k;
+				++n_flips;
 			}
 		}
 
-		if (y_beg[i] < 0) {
-			ver3("obj %d unused", i);
+		if (n_flips == 0) {
+			ver3("vid obj %d unused", i);
 			continue;
 		}
 
-		int32_t x = od[1] & 0x03ff;
-		ver2("obj %d %d:%d %d+%d", i, y_beg[i], y_end[i], x, w);
-	}
-
-	int32_t vcr1 = mem[REG_VCR1];
-	int32_t fon_h = (vcr1 & 0xf000) >> 12;
-
+		if (n_flips == 1) {
+			flips[n_flips] = SCR_H;
+			++n_flips;
+		}
+
+		bool cb = (od[0] & OD0_CB) != 0;
+		bool tde = (od[0] & OD0_TDE) != 0;
+
+		int32_t x = (od[1] & 0x03ff) * 2;
+		int32_t off = ((od[0] & 0x00c0) << 10) | od[2];
+
+		ver2("vid obj %d %c %c %d:%d %d+%d 0x%05x",
+				i, cb ? 'c' : 'b', tde ? 't' : '-', flips[0], flips[1], x, w, off);
+
+		if (!cb) {
+			bm_obj_t *bm_obj = bm_objs + n_bm_objs;
+
+			bm_obj->x = x;
+			bm_obj->y = flips[0];
+			bm_obj->w = w;
+			bm_obj->h = flips[1] - flips[0];
+			bm_obj->mem = mem + off;
+			bm_obj->tran = tde;
+
+			++n_bm_objs;
+		}
+		else {
+			int32_t vcr1 = mem[REG_VCR1];
+			int32_t fon_h = (vcr1 & 0xf000) >> 12;
+		}
+	}
+
+	if (SDL_UnlockMutex(cpu_mutex) < 0) {
+		fail("SDL_UnlockMutex() failed: %s", SDL_GetError());
+	}
+
+	SDL_AtomicAdd(&frame, 1);
 	return false;
 }
@@ -173,9 +338,22 @@
 	}
 
+	uint32_t res;
+
+	if (SDL_LockMutex(cpu_mutex) < 0) {
+		fail("SDL_LockMutex() failed: %s", SDL_GetError());
+	}
+
 	if (off16 >= reg_off && off16 < reg_off + 16) {
-		return mem[off16 - reg_off];
-	}
-
-	return mem[bank * WIN_SZ_W + off16];
+		res = mem[off16 - reg_off];
+	}
+	else {
+		res = mem[bank * WIN_SZ_W + off16];
+	}
+
+	if (SDL_UnlockMutex(cpu_mutex) < 0) {
+		fail("SDL_UnlockMutex() failed: %s", SDL_GetError());
+	}
+
+	return res;
 }
 
@@ -190,15 +368,30 @@
 	}
 
+	if (SDL_LockMutex(cpu_mutex) < 0) {
+		fail("SDL_LockMutex() failed: %s", SDL_GetError());
+	}
+
 	if (off16 == PAL_OFF) {
-		pal[i_pal] = (int32_t)val;
-		i_pal = (i_pal + 1) % 16;
-		return;
-	}
-
-	if (off16 >= reg_off && off16 < reg_off + 16) {
+		int32_t i_pal = ((int32_t)val & 0x03c0) >> 6;
+
+		uint32_t r = ((val & 0x0004) >> 1) | ((val & 0x0020) >> 5);
+		uint32_t g = ((val & 0x0002) >> 0) | ((val & 0x0010) >> 4);
+		uint32_t b = ((val & 0x0001) << 1) | ((val & 0x0008) >> 3);
+
+		r = (r ^ 3) * 85;
+		g = (g ^ 3) * 85;
+		b = (b ^ 3) * 85;
+
+		pal[i_pal] = (r << 24) | (g << 16) | (b << 8) | 0xff;
+	}
+	else if (off16 >= reg_off && off16 < reg_off + 16) {
 		mem[off16 - reg_off] = (uint16_t)val;
-		return;
-	}
-
-	mem[bank * WIN_SZ_W + off16] = (uint16_t)val;
-}
+	}
+	else {
+		mem[bank * WIN_SZ_W + off16] = (uint16_t)val;
+	}
+
+	if (SDL_UnlockMutex(cpu_mutex) < 0) {
+		fail("SDL_UnlockMutex() failed: %s", SDL_GetError());
+	}
+}
