Sem necessidade para um blog

Atualizando a tela com Allegro e com Java

Como disse no post passado, eu ia implementar rotinas para atualização de tela para servir de exemplo. Implementei um programa de exemplo em C que utiliza a biblioteca Allegro, e um em Java utilizando o Java 2D, que já vem no JSE.

C e Allegro

Allegro, para quem ainda não conhece, é uma biblioteca para jogos para a linguagem C e C++ e roda nas plataformas DOS, Unix (Linux, FreeBSD, Irix, Solaris, Darwin), Windows, QNX, BeOS and MacOS X que fornece funções para gráficos, sons, entrada, temporizador, GUI, entre outras coisas. Mais informações na página oficial do Allegro.

O código é muito simples, e além do Allegro usei uma biblioteca de atualização de tela que encontrei na GameDev um tempo atrás que se mostrou muito útil e resolvi aproveitá-la no exemplo. Ela implementa double buffering, page flipping e triple buffering, e estes dois últimos podem ser também com um buffer em memória RAM, caso seu aplicativo use muitas imagens translúcidas. O double buffering também pode ser usando memória RAM ou de sistema. Informações sobre os tipos de memória para a estrutura BITMAP você encontra no manual no início da parte que fala de Bitmap.

O programa fica movendo um quadrado na parte de cima de um lado para o outro, enquanto apresenta informações do modo de atualização, do vsync e instruções para interagir com o programa. O usuário pode desligar o v-sync em alguns modos com a tecla “V” e trocar de modo com as telas de “0″ a “6″. É claramente possível ver os efeitos de flickering e tearing quando o vsync é desligado também. Aparentemente há algum bug que quando troca para triple buffering, ele fica alguns segundos muito lento, mas depois de um tempo ele volta ao normal. Segue o código:

#include <allegro.h>
// Screen Update API for Allegro
// http://www.gamedev.net/reference/articles/article2158.asp
#include "al_screen.h"
 
// count the fps
volatile int fps = 0, frames = 0;
void my_timer_handler()
{
    fps = frames;
    frames = 0;
}
END_OF_FUNCTION(my_timer_handler)
 
// init allegro
int init();
// create bitmap for the moving square
BITMAP *create_square();
// draw text information on buffer
void draw_text(BITMAP *buffer);
// check user input
void check_input();
 
// names of update modes
char update_modes[][80] =
{
    { "UPDATE_NONE" },
    { "UPDATE_TRIPLE_BUFFER" },
    { "UPDATE_PAGE_FLIP" },
    { "UPDATE_SYSTEM_BUFFER" },
    { "UPDATE_DOUBLE_BUFFER" },
    { "UPDATE_TRIPLE_WMB" },
    { "UPDATE_PAGEFLIP_WMB" }
};
 
int main()
{
    BITMAP *buffer;
    BITMAP *square;
    int x, y, vx;
 
    if (init())
    {
        return 1;
    }
 
    square = create_square();
    // initial position and velocity
    x = y = 0;
    vx = 5;
 
    while (!key[KEY_ESC])
    {
        // check user input
        check_input();
        // update the square's position
        x += vx;
        if (x > SCREEN_W - square->w)
        {
            x = SCREEN_W - square->w;
            vx *= -1;
        }
        if (x < 0)
        {
            x = 0;
            vx *= -1;
        }
        // get the buffer
        if (get_update_method())
        {
            buffer = get_buffer();
        }
        else
        {
            buffer = screen;
            if (vsync_is_enabled())
            {
                vsync();
            }
        }
        // draw
        acquire_bitmap(buffer);
        clear(buffer);
        draw_sprite(buffer, square, x, y);
        draw_text(buffer);
        release_bitmap(buffer);
        // show in screen
        if (get_update_method())
        {
            update_screen();
        }
        else
        {
            // já está na tela
        }
        // increment frame count, to show current fps
        frames++;
    }
 
    if (get_update_method())
    {
        shutdown_screen_updating();
    }
 
    return 0;
}
END_OF_MAIN();
 
// init allegro and screen updating
int init()
{
    allegro_init();
    set_uformat(U_ASCII);
    install_keyboard();
    install_timer();
    set_color_depth(32);
    if (set_gfx_mode(GFX_AUTODETECT, 640, 480, 0, 0))
    {
        allegro_message("Falhou");
        return 1;
    }
    if (!initialize_screen_updating(UPDATE_DOUBLE_BUFFER))
    {
        set_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
        allegro_message("Falha no initialize_screen_updating");
        return 1;
    }
    clear_keybuf();
    LOCK_VARIABLE(fps);
    LOCK_VARIABLE(frames);
    LOCK_FUNCTION(my_timer_handler);
    // call my_timer_handler every 1000 ms
    install_int(my_timer_handler, 1000);
    return 0;
}
 
// create and paint the square bitmap
BITMAP *create_square()
{
    BITMAP *square;
    square = create_bitmap(200, 200);
    clear_to_color(square, makecol(200, 200, 200));
    return square;
}
 
// draw some info in the buffer
void draw_text(BITMAP *buffer)
{
    int mode, vsync;
    mode = get_update_method();
    vsync = vsync_is_enabled();
    if (mode == UPDATE_SYSTEM_BUFFER || mode == UPDATE_DOUBLE_BUFFER || mode == UPDATE_NONE)
    {
        textprintf_ex(buffer, font, 10, SCREEN_H - 60, makecol(230, 230, 230), -1, "Vsync - %s", (vsync ? "on" : "off"));
    }
    textprintf_ex(buffer, font, 10, SCREEN_H - 50, makecol(230, 230, 230), -1, "Mode: %d - %s", mode, update_modes[mode]);
    textout_ex(buffer, font, "  V - Toggle vsync on/off", 10, SCREEN_H - 40, makecol(230, 230, 230), -1);
    textout_ex(buffer, font, "0-6 - Change update mode", 10, SCREEN_H - 30, makecol(230, 230, 230), -1);
    textprintf_ex(buffer, font, 10, SCREEN_H - 20, makecol(230, 230, 230), -1, "Fps: %d", fps);
}
 
// check user input
void check_input()
{
    int ch;
    int mode;
    if (keypressed())
    {
        ch = readkey();
        // vsync on/off
        if (ch >> 8 == KEY_V)
        {
            toggle_vsync();
        }
        else
        {
            if ((ch & 0xFF) >= '0' && (ch & 0xFF) <= '6')
            {
                // update modes
                mode = (ch & 0xFF) - '0';
                if (mode)
                {
                    initialize_screen_updating(mode);
                }
                else
                {
                    // this is for UPDATE_NONE
                    shutdown_screen_updating();
                }
            }
        }
    }
}

Java e Java2D

A partir do Java 1.4, apareceu no JSE uma api chamada Full-Screen Exclusive Mode API (inclusive seguindo o link tem vários exemplos melhores do que o meu :P) que permite você trocar de resolução e deixar uma janela em tela cheia. Usarei isso para criar meu exemplo.

Outra coisa que usarei aqui é o chamado Active Rendering, que nada mais é do que você tomar conta da rotina de desenho. Em aplicações tradicionais de GUI, quem decide quando desenhar é o Sistema Operacional.

Nesse código, eu entro em modo de tela cheia com o primeiro modo possível, retornado pelo método getDisplayModes() da classe GraphicsDevice. Logo depois da inicialização, o método run() que tem o loop principal é chamado, criando a estratégia de atualização e movendo um quadrado pela tela. O programa ainda apresenta na tela a taxa de quadros.

Uma coisa importante para se observar aqui é o uso do método contentsLost() da classe BufferStrategy. Isso é necessário pois o usuário pode dar um Alt+TAB e depois voltar, e caso o programa tente desenhar em um buffer que perdeu, causará uma exceção e a interrupção do programa.

Segue então o código:

package com.dudaskank.screenupdate;
 
import java.awt.Color;
import java.awt.DisplayMode;
import java.awt.Graphics;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.image.BufferStrategy;
import java.util.Timer;
import java.util.TimerTask;
 
import javax.swing.JFrame;
 
public class Main extends JFrame {
	private static final long serialVersionUID = 1L;
	private int frames = 0, fps = 0;
 
	public Main() {
		// iniciando a tela cheia
		super("Screen Update Test");
		setResizable(false);
		setUndecorated(true);
		// para usar active rendering
		setIgnoreRepaint(true);
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		GraphicsEnvironment env = GraphicsEnvironment
				.getLocalGraphicsEnvironment();
		GraphicsDevice device = env.getDefaultScreenDevice();
 
		DisplayMode[] modes = device.getDisplayModes();
		device.setFullScreenWindow(this);
		// pega o primeiro modo disponível em ela cheia
		device.setDisplayMode(modes[0]);
 
 
 
		// timer para zerar a quantidade atual de frames a atualizar o fps
		Timer timer = new Timer(true);
		timer.scheduleAtFixedRate(new TimerTask() {
			@Override
			public void run() {
				fps = frames;
				frames = 0;
			}
		}, 0, 1000);
	}
 
	public void run() {
		// cria 2 bitmaps, um para a tela sendo exibida e outro para desenhar
		// fora da tela (double buffering ou page flipping)
		createBufferStrategy(2);
		BufferStrategy strategy = getBufferStrategy();
		Graphics g;
		// variáveis usadas na movimentação do quadrado
		int x, y, vx, vy;
		x = y = 0;
		vx = vy = 5;
		// loop até fechar a janela
		while (true) {
			// atualiza a posição do quadrado
			x += vx;
			y += vy;
			if (x < 0) {
				x = 0;
				vx *= -1;
			} else if (x + 100 > getWidth()) {
				x = getWidth() - 100;
				vx *= -1;
			}
			if (y < 0) {
				y = 0;
				vy *= -1;
			} else if (y + 100 > getHeight()) {
				y = getHeight() - 100;
				vy *= -1;
			}
			// desenha um quadrado
			g = strategy.getDrawGraphics();
			if (!strategy.contentsLost()) {
				g.setColor(Color.BLACK);
				g.fillRect(0, 0, getWidth(), getHeight());
				g.setColor(Color.GRAY);
				g.fillRect(x, y, 100, 100);
				// escreve o fps na tela
				g.setColor(Color.BLUE);
				g.drawString("Fps: " + fps, 10, 50);
				g.drawString("Alt+F4 para sair", 10, 60);
				g.dispose();
				strategy.show();
				// incrementa número de frames exibidos
				frames++;
			}
		}
	}
 
	public static void main(String[] args) {
		Main main = new Main();
		main.setVisible(true);
		main.run();
	}
}

Conclusão

Vendo um exemplo de implementação agora tenho certeza que ficou mais fácil de entender o último post aqui do blog. Espero que tenham gostado, críticas e dúvidas por favor mandem os comentários, assim que possível irei respondendo :).

3 comentários para “Atualizando a tela com Allegro e com Java”

  1. clarissa gomes disse:

    Adoroo esse tipo de coisaaaa

  2. Rafael disse:

    Como faço para exibir uma imagem que a resolução´é maior que 800 X 600? Comsiguo definir esta resolução no set_gfx_mode(GFX_AUTODETECT, ???, ???, 0, 0))

  3. dudaskank disse:

    Rafael, resolução 800×600 você consegue no set_gfx_mode() sim.

    Para você colocar uma maior dentro da tela, você deve usar a função stretch_blit().

    A documentação destas e de outras funções você encontra no seu diretório docs do allegro ou em http://www.allegro.cc/manual/

Deixe um comentário