void generate_world(World* world, Tile_Info* info, isize ti_count, uint64 seed, Memory_Arena* arena)
{
	Random r_s;
	Random* r = &r_s;
	init_random(r, seed);


	for(isize i = 0; i < world->areas_height; ++i) {
		for(isize j = 0; j < world->areas_width; ++j) {
			isize index = i * world->areas_width + j;
			World_Area* area = world->areas + index;
			init_world_area(area, arena);
			area->map.info = info;
			area->map.info_count = ti_count;
			generate_tilemap(&area->map, next_random_uint64(r));
			for(isize i = 0; i < World_Area_Tilemap_Width; ++i) {
				Entity* e = world_area_get_next_entity(area);
				Sim_Body* b = sim_find_body(&area->sim, e->body_id);
				e->sprite.texture = Get_Texture_Coordinates(0, 96, 32, 64);
				b->shape.hw = 16;
				b->shape.hh = 12;
				b->inv_mass = 1.0f;
				e->sprite.size = v2(32, 64);
				e->sprite.center = v2(0, 20);
				entity_add_event_on_activate(e, test_on_activate);
				do {
					b->shape.center = v2(
						rand_range(r, 0, area->map.w * 32),
						rand_range(r, 0, area->map.h * 32));
				}
				while (info[tilemap_get_at(&area->map, b->shape.center)].solid);
			}

			generate_statics_for_tilemap(&area->sim, &area->map);

			isize north_link = modulus(i - 1, world->areas_height) * world->areas_width + j;
			isize south_link = modulus(i + 1, world->areas_height) * world->areas_width + j;
			isize west_link = i * world->areas_width + modulus(j - 1, world->areas_width);
			isize east_link = i * world->areas_width + modulus(j + 1, world->areas_width);
			area->north = Area_Link {
				v2i(World_Area_Tilemap_Width / 2,  World_Area_Tilemap_Height - 1), 
				world->areas + north_link
			};
			area->south = Area_Link {
				v2i(World_Area_Tilemap_Width / 2, 1),
				world->areas + south_link
			};
			area->west = Area_Link {
				v2i(World_Area_Tilemap_Width - 1, World_Area_Tilemap_Height / 2),
				world->areas + west_link
			};
			area->east = Area_Link {
				v2i(1, World_Area_Tilemap_Height / 2),
				world->areas + east_link
			};
		}
	}

}
Beispiel #2
0
void VirtualMachine::InitGuiSettings()
{
	m_showSettingsGui = false;

	const float minVal = 0.0f;
	const float maxVal = 16.0f;
	const int width = 200;
	const int height = 100;
	const int numVals = 128;

	m_lineGraphUpdate = new LineGraph( minVal, maxVal, v2i(width, height), numVals );
	m_lineGraphMemory = new LineGraph( minVal, maxVal, v2i(width, height), numVals );
}
Beispiel #3
0
void Core::InitGuiGraphs()
{
	m_showGraphsGui = false;

	const float minVal = 0.0f;
	const float maxVal = 1000.0f / m_fps;
	const int width = 200;
	const int height = 100;
	const int numVals = 128;

	m_lineGraphRender = new LineGraph( minVal, maxVal, v2i(width, height), numVals );
	m_lineGraphUpdate = new LineGraph( minVal, maxVal, v2i(width, height), numVals );
	m_lineGraphTotal = new LineGraph( minVal, maxVal, v2i(width, height), numVals );
	m_lineGraphGpu = new LineGraph( minVal, maxVal, v2i(width, height), numVals );
}
Beispiel #4
0
void animation::update (float dt) {
	m_time_to_next_frame -= dt;
	picture_wind *par = (picture_wind *)m_parent;
	v2i pos = (in.mouse.pos - v2i (10,10) - par->m_pos) / par->m_scale;
	Rect <int> r (v2i (240, 70), v2i (11, 11));
	if (r << in.mouse.pos && in.mouse.mbutton [MOUSE_LEFT].just_pressed) {
		m_prev_layer_half_transparent = par->m_layers.back ();
		FOR_ARRAY_2D (v, m_prev_layer_half_transparent) {
			m_prev_layer_half_transparent[v].a /= 2;
		}

		par->m_layers.push_back (rgba_array (true));
		par->m_layers.back ().init (D_W, D_H);
		par->m_layers.back ().clear (CLR (0,0,0,0));
		par->m_cur_layer = par->m_layers.size () - 1;
	}
Beispiel #5
0
void Core::GuiGraphs()
{
	const v2i pos = v2i(300, Window::Get()->Sizei().y - 20 );

	Imgui::Begin("Main Analytics", pos );
	Imgui::Print("Total");
	Imgui::LineGraph(m_lineGraphTotal);
	Imgui::Print("Update");
	Imgui::LineGraph(m_lineGraphUpdate);
	Imgui::Print("Render");
	Imgui::LineGraph(m_lineGraphRender);
	Imgui::Print("GPU");
	Imgui::LineGraph(m_lineGraphGpu);
	Imgui::End();
}
Beispiel #6
0
void load_assets()
{
	isize w, h;
	renderer->texture = ogl_load_texture("data/graphics.png", &w, &h);
	renderer->texture_width = w;
	renderer->texture_height = h;

	game->body_font = load_spritefont("data/gohufont-14.glyphs", 
			v2i(2048 - 1142, 0));
	body_font = game->body_font;

	game->state = Game_State_Play;
	play_state_init();
	play_state_start();
#if DEBUG
	//load_test_assets();
	//game->state = Game_State_None;
#endif
}
Beispiel #7
0
void Core::GuiStats()
{
	const int padding = 5;
	const v2i windowDimen = Window::Get()->Sizei();
	const float deltaMs = 1000.0f / m_fps;

	Imgui::Begin("Analytics", v2i(padding, windowDimen.y-padding) );
	Imgui::Minimize();
	Imgui::FillBarFloat("Total", m_msUpdate+m_msRender, 0.0f, deltaMs );
	Imgui::FillBarFloat("Update", m_msUpdate, 0.0f, deltaMs );
	Imgui::FillBarFloat("Render", m_msRender, 0.0f, deltaMs );
	Imgui::FillBarFloat("GPU", m_msGpu, 0.0f, deltaMs );
	Imgui::CheckBox("Show Graphs", m_showGraphsGui );
	VirtualMachine::Get()->GuiStats();
	SoundMngr::Get()->GuiStats();
	Imgui::End();	

	if ( m_showGraphsGui ) 
	{
		UpdateGuiGraphs();
		GuiGraphs();
	}
 }
Beispiel #8
0
void VirtualMachine::GuiSettings()
{
	m_lineGraphUpdate->PushVal( m_updateMs );
	m_lineGraphUpdate->SetMaxVal(m_dt*1000.0f);
	m_lineGraphMemory->SetMaxVal( (float)m_vm->GetDesiredByteMemoryUsageHard() );
	m_lineGraphMemory->PushVal( (float)m_vm->GetCurrentMemoryUsage() );

	int workPerIncrement = m_vm->GetGC()->GetWorkPerIncrement();
	int destructPerIncrement = m_vm->GetGC()->GetDestructPerIncrement();
	int memUsageSoft = m_vm->GetDesiredByteMemoryUsageSoft();
	int memUsageHard = m_vm->GetDesiredByteMemoryUsageHard();

	const v2i pos = v2i(300, Window::Get()->Sizei().y - 20 );

	Imgui::Begin("GameMonkey Settings", pos);
	Imgui::Print("Update");
	Imgui::LineGraph( m_lineGraphUpdate );
	Imgui::Print("Memory");
	Imgui::LineGraph( m_lineGraphMemory );
	Imgui::FillBarInt("Mem Usage (Bytes)", m_vm->GetCurrentMemoryUsage(), 0, m_vm->GetDesiredByteMemoryUsageHard() );
	Imgui::Header("Garbage Collector");
	Imgui::SliderInt( "Work Per Increment", workPerIncrement, 1, 600 );
	Imgui::SliderInt( "Destructs Per Increment", destructPerIncrement, 1, 600 );
	Imgui::SliderInt( "Mem Usage Soft", memUsageSoft, 200000, memUsageHard );
	Imgui::SliderInt( "Mem Usage Hard", memUsageHard, memUsageSoft+500, memUsageSoft+200000 );
	Imgui::Separator();
	Imgui::FillBarInt("GC Warnings", m_vm->GetStatsGCNumWarnings(), 0, 200 );
	Imgui::FillBarInt("GC Full Collects", m_vm->GetStatsGCNumFullCollects(), 0, 200 );
	Imgui::FillBarInt("GC Inc Collects", m_vm->GetStatsGCNumIncCollects(), 0, 200 );
	Imgui::End();

	m_vm->SetDesiredByteMemoryUsageSoft(memUsageSoft);
	m_vm->SetDesiredByteMemoryUsageHard(memUsageHard);
	m_vm->GetGC()->SetWorkPerIncrement(workPerIncrement);
	m_vm->GetGC()->SetDestructPerIncrement(destructPerIncrement);
}
Beispiel #9
0
v2i Font::CalcDimen( const char* a_text ) const
{
	return v2i(CalcWidth(a_text), CalcHeight() );
}
Beispiel #10
0
void VirtualMachine::GuiThreadAllocations()
{
	const v2i pos = v2i(300, Window::Get()->Sizei().y - 20 );

	Imgui::Begin("Thread Allocations (Live)", pos);
	m_freezeThreadAllocationsGui = Imgui::CheckBox("Freeze", m_freezeThreadAllocationsGui);

	if (!m_freezeThreadAllocationsGui)
	{
		std::map<const gmFunctionObject*, ThreadAllocationItem>::iterator i = m_threadAllocationsHistory.begin();
		while (i != m_threadAllocationsHistory.end())
		{
			i->second.eraseCountdown -= 1;

			if (i->second.eraseCountdown <= 0)
			{
				i = m_threadAllocationsHistory.erase(i);
			}
			else
			{
				++i;
			}
		}
	}

	if (!m_freezeThreadAllocationsGui)
	{
		std::map<const gmFunctionObject*, int>::const_iterator a = m_vm->GetAllocCountIteratorBegin();
		std::map<const gmFunctionObject*, int>::const_iterator b = m_vm->GetAllocCountIteratorEnd();
		for (; a != b; ++a)
		{
			ThreadAllocationItem& item = m_threadAllocationsHistory[a->first];
			item.allocations = a->second;
			item.eraseCountdown = 300;
		}
	}

	{
		std::map<const gmFunctionObject*, ThreadAllocationItem>::const_iterator a = m_threadAllocationsHistory.begin();
		std::map<const gmFunctionObject*, ThreadAllocationItem>::const_iterator b = m_threadAllocationsHistory.end();
		for (; a != b; ++a)
		{
			const gmFunctionObject* func = a->first;
			const gmuint32 sourceid = func->GetSourceId();
			const char* sourcecode = NULL;
			const char* filename = NULL;
			m_vm->GetSourceCode(sourceid, sourcecode, filename);

			int allocations = a->second.allocations;
			if (allocations <= 0)
				continue;

			char buffer[128] = { '.' };
			const char* fname = func->GetDebugName();
			const char* status = a->second.eraseCountdown == 300 ? "new" : "old";
			sprintf(buffer, "%s:%s [%s]", filename, fname, status);
			int len = strlen(buffer);
			memset( &buffer[len], '.', sizeof(buffer)-len-1);
			itoa(allocations, buffer+50, 10);

			Imgui::Print(buffer);
		}
	}

	Imgui::End();
}
Beispiel #11
0
void Input::SetMousePos( int x, int y )
{
    SDL_WarpMouse( x, Window::Get()->Sizei().y - y );
    m_mousePos = v2i(x,y);
}
Beispiel #12
0
int main(int argc, char** argv)
{
	//stbi_set_flip_vertically_on_load(1);

	if(SDL_Init(SDL_INIT_EVERYTHING) != 0) {
		Log_Error("Could not init SDL"); 
		Log_Error(SDL_GetError());
		return 1;
	}

	SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
	SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
	SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
	SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
	SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
	SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
	SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
	SDL_GL_SetAttribute(SDL_GL_FRAMEBUFFER_SRGB_CAPABLE, 1);
	SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
	SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
	

	int32 window_display_index = 0;
#if 1
	window_display_index = 1;
#endif
	SDL_Window* window = SDL_CreateWindow("Rituals", 
			SDL_WINDOWPOS_CENTERED_DISPLAY(window_display_index), 
			SDL_WINDOWPOS_CENTERED_DISPLAY(window_display_index),
			1280, 720, 
			SDL_WINDOW_OPENGL | 
			SDL_WINDOW_RESIZABLE |
			SDL_WINDOW_MOUSE_FOCUS |
			SDL_WINDOW_INPUT_FOCUS);

	if(window == NULL) {
		Log_Error("Could not create window");
		Log_Error(SDL_GetError());
		return 1;
	}

	printf("%s \n", SDL_GetError());
	SDL_GLContext glctx = SDL_GL_CreateContext(window);

	if(ogl_LoadFunctions() == ogl_LOAD_FAILED) {
		Log_Error("Could not load OpenGL 3.3 functions...");
		return 1;
	}

	int ret = SDL_GL_SetSwapInterval(-1);

	{
#define _check_gl_attribute(attr, val) int _##attr##_val; \
	int _##attr##_success = SDL_GL_GetAttribute(attr, &_##attr##_val); \
	gl_checks[gl_check_count++] = _##attr##_val == val; \
	gl_names[gl_check_count - 1] = #attr; \
	gl_vals[gl_check_count - 1] = _##attr##_val; \
	gl_exp_vals[gl_check_count - 1] = val; 
			 
		//check if we got everything
		bool gl_checks[64];
		char* gl_names[64];
		int gl_vals[64];
		int gl_exp_vals[64];
		isize gl_check_count = 0;

		_check_gl_attribute(SDL_GL_RED_SIZE, 8);
		_check_gl_attribute(SDL_GL_GREEN_SIZE, 8);
		_check_gl_attribute(SDL_GL_BLUE_SIZE, 8);
		_check_gl_attribute(SDL_GL_ALPHA_SIZE, 8);
		_check_gl_attribute(SDL_GL_DOUBLEBUFFER, 1);
		_check_gl_attribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
		_check_gl_attribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
		_check_gl_attribute(SDL_GL_FRAMEBUFFER_SRGB_CAPABLE, 1);
		_check_gl_attribute(SDL_GL_ACCELERATED_VISUAL, 1);
		_check_gl_attribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);

		for(isize i = 0; i < gl_check_count; ++i) {
			printf("%s %s: wanted %d, got %d \n", 
					gl_names[i], 
					gl_checks[i] ? "succeeeded" : "failed", 
					gl_exp_vals[i], 
					gl_vals[i]);
		}

	}	

	// Game initializiation
	game = Allocate(Game, 1);
	{
		game->window = window;
		game->state = Game_State_None;
		game->meta_arena = Allocate(Memory_Arena, 1);
		init_memory_arena(game->meta_arena, isz(Memory_Arena) * 10);
		game->game_arena = new_memory_arena(Kilobytes(64), game->meta_arena);
		game->asset_arena = new_memory_arena(Megabytes(512), game->meta_arena);
		game->temp_arena = new_memory_arena(Megabytes(64), game->meta_arena);
		game->play_arena = new_memory_arena(Megabytes(512), game->meta_arena);
		game->renderer_arena = new_memory_arena(Megabytes(32), game->meta_arena);

		game->base_path = SDL_GetBasePath();
		game->base_path_length = strlen(game->base_path);

		game->input = Arena_Push_Struct(game->game_arena, Game_Input);
		game->input->scancodes = Arena_Push_Array(game->game_arena, int8, SDL_NUM_SCANCODES);
		game->input->keycodes = Arena_Push_Array(game->game_arena, int8, SDL_NUM_SCANCODES);
		game->input->mouse = Arena_Push_Array(game->game_arena, int8, 16);

		init_random(&game->r, time(NULL));
		//TODO(will) load window settings from file
		game->window_size = v2i(1280, 720);
		game->scale = 1.0f;

		game->renderer = Arena_Push_Struct(game->game_arena, Renderer);
		renderer_init(game->renderer, game->renderer_arena);

		renderer = game->renderer;
		input = game->input;
	}

	load_assets();

	bool running = true;
	SDL_Event event;
	glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
	//glClearColor(1, 1, 1, 1);

	play_state->current_time = SDL_GetTicks();
	play_state->prev_time = play_state->current_time;
	while(running) {
		uint64 start_ticks = SDL_GetTicks();

		if(game->input->num_keys_down < 0) game->input->num_keys_down = 0;
		if(game->input->num_mouse_down < 0) game->input->num_mouse_down = 0;

		if(game->input->num_keys_down > 0)
		for(int64 i = 0; i < SDL_NUM_SCANCODES; ++i) {
			int8* t = game->input->scancodes + i;
			if(*t == State_Just_Released) {
				*t = State_Released;
			} else if(*t == State_Just_Pressed) {
				*t = State_Pressed;
			}
			t = game->input->keycodes + i;
			if(*t == State_Just_Released) {
				*t = State_Released;
			} else if(*t == State_Just_Pressed) {
				*t = State_Pressed;
			}
		}
		if(game->input->num_mouse_down > 0)
		for(int64 i = 0; i < 16; ++i) {
			int8* t = game->input->mouse + i;
			if(*t == State_Just_Released) {
				*t = State_Released;
			} else if(*t == State_Just_Pressed) {
				*t = State_Pressed;
			}
		}


		while(SDL_PollEvent(&event)) {
			//TODO(will) handle text input
			switch(event.type) {
				case SDL_QUIT:
					running = false;
					break;
				case SDL_WINDOWEVENT:
					update_screen();
					break;
				case SDL_KEYDOWN:
					game->input->num_keys_down++;
					if(!event.key.repeat) {
						game->input->scancodes[event.key.keysym.scancode] = State_Just_Pressed;
						if(event.key.keysym.sym < SDL_NUM_SCANCODES) {
							game->input->keycodes[event.key.keysym.sym] = State_Just_Pressed;
						}
					}
					break;
				case SDL_KEYUP:
					game->input->num_keys_down--;
					if(!event.key.repeat) {
						game->input->scancodes[event.key.keysym.scancode] = State_Just_Released;
						if(event.key.keysym.sym < SDL_NUM_SCANCODES) {
							game->input->keycodes[event.key.keysym.sym] = State_Just_Released;
						}
					}
					break;
				case SDL_MOUSEBUTTONDOWN:
					game->input->num_mouse_down++;
					game->input->mouse[event.button.button] = State_Just_Pressed;
					break;
				case SDL_MOUSEBUTTONUP:
					game->input->num_mouse_down--;
					game->input->mouse[event.button.button] = State_Just_Released;
					break;
			}
		}
	
		int mx, my;
		SDL_GetMouseState(&mx, &my);
		input->mouse_x = mx;
		input->mouse_y = my;

		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

		update();

		SDL_GL_SwapWindow(window);
		uint64 frame_ticks = SDL_GetTicks() - start_ticks;
		//if(frame_ticks > 18) printf("Slow frame! %d\n", frame_ticks);
	}

	SDL_Quit();
	return 0;
}
Beispiel #13
0
void    TileViewer_Update(t_app_tile_viewer *app)
{
    ALLEGRO_BITMAP *bmp = app->box->gfx_buffer;

    // Skip update if not active
    if (!app->active)
        return;

    // If skin has changed, redraw everything
    if (app->box->flags & GUI_BOX_FLAGS_DIRTY_REDRAW_ALL_LAYOUT)
    {
        TileViewer_Layout(app, FALSE);
        app->box->flags &= ~GUI_BOX_FLAGS_DIRTY_REDRAW_ALL_LAYOUT;
        app->dirty = TRUE;
    }

    bool dirty_all = app->dirty || Palette_EmulationDirtyAny;
    bool dirty = dirty_all;

    // Update hovered tile index
    {
        const int mx = app->tiles_display_zone->mouse_x;
        const int my = app->tiles_display_zone->mouse_y;
        // Msg(MSGT_USER, "mx = %d, my = %d", mx, my);
        if (app->tiles_display_zone->mouse_action & WIDGET_MOUSE_ACTION_HOVER)
            app->tile_hovered = ((my / 8) * 16) + mx / 8;
        else
            app->tile_hovered = -1;
    }

    // Compute the tile that is to display in the bottom info line
    int tile_current = (app->tile_hovered != -1) ? app->tile_hovered : app->tile_selected;
    bool tile_current_refresh = /*(tile_current == -1) ? FALSE : */ (((tile_current != app->tile_displayed) || dirty_all || tgfx.Tile_Dirty [tile_current]));
    int tile_current_addr = -1;
	
	const v2i tiles_frame_pos = app->tiles_display_frame.pos;
	const v2i tile_selected_pos = v2i(app->tile_selected_frame.pos.x + 2, app->tile_selected_frame.pos.y + 2);

	int vram_addr_min = 0x0000;
	int vram_addr_size = 0;
	int vram_tile_size = 1;

    // Then redraw all tiles
	ALLEGRO_LOCKED_REGION* locked_region = al_lock_bitmap(app->box->gfx_buffer, ALLEGRO_PIXEL_FORMAT_ANY, ALLEGRO_LOCK_READWRITE);
    switch (g_driver->vdp)
    {
    case VDP_SMSGG:
        {
			widget_set_enabled(app->vram_addr_tms9918_scrollbar, false);
			vram_addr_min = 0;
			vram_addr_size = 0x4000;
			vram_tile_size = 32;

            int n = 0;
            const u8 *    nd = &tgfx.Tile_Decoded[0][0];
            const u32 *   palette_host = app->palette ? &Palette_EmulationToHostGui[16] : &Palette_EmulationToHostGui[0];
            for (int y = 0; y != app->tiles_height; y++)
			{
                for (int x = 0; x != app->tiles_width; x++)
                {
                    if (tgfx.Tile_Dirty [n] & TILE_DIRTY_DECODE)
                        Decode_Tile (n);
                    if (dirty_all || tgfx.Tile_Dirty [n])
                    {
                        VDP_Mode4_DrawTile(app->box->gfx_buffer, locked_region, nd, palette_host, tiles_frame_pos.x+(x * 8), tiles_frame_pos.y+(y * 8), 0);
                        tgfx.Tile_Dirty [n] = 0;
                        dirty = TRUE;
                    }
                    if (n == tile_current)
					{
                        tile_current_addr = vram_addr_min + (n * 32);
						VDP_Mode4_DrawTile(app->box->gfx_buffer, locked_region, nd, palette_host, tile_selected_pos.x, tile_selected_pos.y, 0);
					}
                    n ++;
                    nd += 64;
                }
			}
            break;
        }
    case VDP_TMS9918:
        {
			widget_set_enabled(app->vram_addr_tms9918_scrollbar, true);
			vram_addr_min = 0x0000 + app->vram_addr_tms9918_current*0x1000;
			vram_addr_size = 0x1000;
			vram_tile_size = 8;

			const int fg_color = Palette_EmulationToHostGui[app->palette + 1];
            const int bg_color = Palette_EmulationToHostGui[(app->palette != 0) ? 1 : 15];
            const u8 * addr = VRAM + vram_addr_min;
			//VRAM = g_machine.VDP.sg_pattern_gen_address;
            // addr = &VRAM[apps.opt.Tiles_Base];
           
            int n = 0;
            for (int y = 0; y != app->tiles_height; y ++)
			{
                for (int x = 0; x != app->tiles_width; x ++)
                {
                    if ((addr - VRAM) > 0x4000)
                        break;
                    VDP_Mode0123_DrawTile(bmp, locked_region, tiles_frame_pos.x+(x * 8), tiles_frame_pos.y+(y * 8), addr, fg_color, bg_color);
                    if (n == tile_current)
					{
                        tile_current_addr = vram_addr_min + (n * 8);
						VDP_Mode0123_DrawTile(bmp, locked_region, tile_selected_pos.x, tile_selected_pos.y, addr, fg_color, bg_color);
					}

                    n++;
                    addr += 8;
                }
			}
            dirty = TRUE; // to be replaced later
            break;
        }
    }
	al_unlock_bitmap(app->box->gfx_buffer);

	// Refresh top status line (address range)
	al_set_target_bitmap(bmp);
	{
		// FIXME-OPT
		const int y = -1;
		al_draw_filled_rectangle(0, y + 1, app->vram_addr_tms9918_scrollbar->enabled ? app->vram_addr_tms9918_scrollbar->frame.pos.x-1 : 128-1, y + 11+1, COLOR_SKIN_WINDOW_BACKGROUND);

		char buf[64];
		sprintf(buf, "Range: $%04X-$%04X", vram_addr_min, vram_addr_min+vram_addr_size-1);
		Font_Print(FONTID_SMALL, buf, 0, y + 1, COLOR_SKIN_WINDOW_TEXT);
		dirty = true;
	}

    // Refresh bottom status line (selected tile)
    if (dirty_all || tile_current_refresh)
    {
		const int y = app->tiles_display_frame.GetMax().y;

        al_draw_filled_rectangle(16, y + 1, 127+1, y + 11+1, COLOR_SKIN_WINDOW_BACKGROUND);
        dirty = TRUE;

        if (tile_current != -1)
        {
            // Description
            char addr[16];
            if (tile_current_addr != -1)
                sprintf(addr, "$%04X", tile_current_addr);
            else
                sprintf(addr, "????");

			char buf[128];
			const int tile_index = tile_current_addr / vram_tile_size;
            sprintf(buf, Msg_Get(MSG_TilesViewer_Tile), tile_index, tile_index, addr);
            Font_Print(FONTID_SMALL, buf, 16, y + 1, COLOR_SKIN_WINDOW_TEXT);
            app->tile_displayed = tile_current;
        }
        else
        {
            // Fill tile with black
			const t_frame* fr = &app->tile_selected_frame;
            al_draw_filled_rectangle(fr->pos.x+2, fr->pos.y+2, fr->pos.x+2+8, fr->pos.y+2+8, COLOR_BLACK);
        }
    }

    if (dirty_all || dirty)
        app->dirty = FALSE;
}