[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Gnash-dev] [PATCH] Initial implementation of a cairo renderer
From: |
Timothy Lee |
Subject: |
[Gnash-dev] [PATCH] Initial implementation of a cairo renderer |
Date: |
Tue, 02 May 2006 15:45:40 +0800 |
User-agent: |
Thunderbird 3.0a1 (X11/20060423) |
Dear all,
I've implementation a cairo renderer that is semi-working. At least
following bugs are known to exist:
* Bitmap offsets are wrong
* Bitmaps are not scaled correctly
* Bitmaps cannot be modulated by a color
I also came across the following issues during the implementation:
* ogl is hardcoded as the renderer in Makefile and gnash.cpp. I
guess work in configure script is needed.
* The use of TRIANGLE_STRIP in draw_mesh_strip() is non-optimal for
cairo. Passing the polygon directly is more efficient. Maybe
extend render_handler class with draw_polygon(), and call
prefer_polygon() to determine whether draw_polygon() should be used?
My knowledge in cairo is limited. It is my hope that the code can be
further extended by others to make the cairo renderer on par with the
OpenGL one.
Regards,
Timothy
diff -u -r1.20 Makefile.am
--- backend/Makefile.am 5 Apr 2006 01:17:45 -0000 1.20
+++ backend/Makefile.am 2 May 2006 07:26:44 -0000
@@ -76,7 +76,7 @@
lib_LTLIBRARIES = libgnashbackend.la
libgnashbackend_la_SOURCES = \
- render_handler_ogl.cpp \
+ render_handler_cairo.cpp \
sound_handler_sdl.cpp \
gtksup.cpp gtksup.h
diff -u -r1.28 gnash.cpp
--- backend/gnash.cpp 30 Apr 2006 21:14:23 -0000 1.28
+++ backend/gnash.cpp 2 May 2006 07:26:44 -0000
@@ -384,7 +384,7 @@
gnash::set_sound_handler(sound);
#endif
}
- render = gnash::create_render_handler_ogl();
+ render = gnash::create_render_handler_cairo();
gnash::set_render_handler(render);
}
diff -u -r1.16 gnash.h
--- server/gnash.h 24 Apr 2006 23:05:56 -0000 1.16
+++ server/gnash.h 2 May 2006 07:26:44 -0000
@@ -164,6 +164,7 @@
// version of the library, depending on platform etc.
render_handler* create_render_handler_xbox();
render_handler* create_render_handler_ogl();
+render_handler* create_render_handler_cairo();
sound_handler* create_sound_handler_sdl();
// render_handler_cairo.cpp -- Timothy Lee <address@hidden> 2006
// This source code has been donated to the Public Domain. Do
// whatever you want with it.
// A render_handler that uses cairo
#include <cairo/cairo.h>
#include <cairo/cairo-xlib.h>
#include "gnash.h"
#include "types.h"
#include "image.h"
#include "utility.h"
#include "log.h"
using namespace gnash;
static cairo_t* g_cr_win = 0;
static cairo_t* g_cr = 0;
Window g_cairo_xwin = 0;
// bitmap_info_cairo declaration
struct bitmap_info_cairo : public gnash::bitmap_info
{
// Cairo image surface
unsigned char* m_buffer;
cairo_surface_t* m_image;
bitmap_info_cairo();
bitmap_info_cairo(int width, int height, Uint8* data);
bitmap_info_cairo(image::rgb* im);
bitmap_info_cairo(image::rgba* im);
~bitmap_info_cairo() {
if (m_texture_id)
cairo_pattern_destroy((cairo_pattern_t*)m_texture_id);
if (m_image) cairo_surface_destroy(m_image);
if (m_buffer) delete [] m_buffer;
}
};
struct render_handler_cairo : public gnash::render_handler
{
// Some renderer state.
cairo_t* m_cr_offscreen;
cairo_t* m_cr_mask;
int m_view_width;
int m_view_height;
// Enable/disable antialiasing.
bool m_enable_antialias;
// Output size.
float m_display_width;
float m_display_height;
gnash::matrix m_current_matrix;
gnash::cxform m_current_cxform;
void set_antialiased(bool enable) {
m_enable_antialias = enable;
}
struct fill_style
{
enum mode
{
INVALID,
COLOR,
BITMAP_WRAP,
BITMAP_CLAMP,
LINEAR_GRADIENT,
RADIAL_GRADIENT,
};
mode m_mode;
gnash::rgba m_color;
const gnash::bitmap_info* m_bitmap_info;
gnash::matrix m_bitmap_matrix;
gnash::cxform m_bitmap_color_transform;
bool m_has_nonzero_bitmap_additive_color;
fill_style()
:
m_mode(INVALID),
m_has_nonzero_bitmap_additive_color(false)
{
}
// Push our style into cairo.
void apply(/*const matrix& current_matrix*/) const
{
// GNASH_REPORT_FUNCTION;
assert(m_mode != INVALID);
if (m_mode == COLOR)
{
apply_color(m_color);
}
else if (m_mode == BITMAP_WRAP || m_mode == BITMAP_CLAMP)
{
assert(m_bitmap_info != NULL);
apply_color(m_color);
if (m_bitmap_info != NULL)
{
// Set up the texture for rendering.
{
// Do the modulate part of the color
// transform in the first pass. The
// additive part, if any, needs to
// happen in a second pass.
// FIXME!!! bitmap cannot be modulated by RGB
cairo_set_source_rgba(g_cr,
m_bitmap_color_transform.m_[0][0],
m_bitmap_color_transform.m_[1][0],
m_bitmap_color_transform.m_[2][0],
m_bitmap_color_transform.m_[3][0]);
}
cairo_pattern_t* pattern =
(cairo_pattern_t*)m_bitmap_info->m_texture_id;
if (m_mode == BITMAP_CLAMP)
{
cairo_pattern_set_extend(pattern, CAIRO_EXTEND_NONE);
}
else
{
cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT);
}
// Set up the bitmap matrix
// FIXME!!! scaling and offset is wrong
cairo_matrix_t mat;
const gnash::matrix& m = m_bitmap_matrix;
cairo_matrix_init(&mat, m.m_[0][0], m.m_[1][0],
m.m_[0][1], m.m_[1][1], m.m_[0][2], m.m_[1][2]);
cairo_pattern_set_matrix(pattern, &mat);
cairo_set_source(g_cr, pattern);
}
}
}
// Return true if we need to do a second pass to make
// a valid color. This is for cxforms with additive
// parts; this is the simplest way (that we know of)
// to implement an additive color with stock OpenGL.
bool needs_second_pass() const
{
if (m_mode == BITMAP_WRAP || m_mode == BITMAP_CLAMP)
{
return m_has_nonzero_bitmap_additive_color;
}
else
{
return false;
}
}
// Set OpenGL state for a necessary second pass.
void apply_second_pass() const
{
assert(needs_second_pass());
// The additive color also seems to be modulated by the
texture. So,
// maybe we can fake this in one pass using using the mean
value of
// the colors: c0*t+c1*t = ((c0+c1)/2) * t*2
// I don't know what the alpha component of the color is for.
cairo_set_source_rgba(g_cr,
m_bitmap_color_transform.m_[0][1] / 255.0f,
m_bitmap_color_transform.m_[1][1] / 255.0f,
m_bitmap_color_transform.m_[2][1] / 255.0f,
m_bitmap_color_transform.m_[3][1] / 255.0f);
/*
glBlendFunc(GL_ONE, GL_ONE);
*/
}
void cleanup_second_pass() const
{
/*
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
*/
}
void disable() { m_mode = INVALID; }
void set_color(gnash::rgba color) { m_mode = COLOR; m_color =
color; }
void set_bitmap(const gnash::bitmap_info* bi, const gnash::matrix&
m, bitmap_wrap_mode wm, const gnash::cxform& color_transform)
{
m_mode = (wm == WRAP_REPEAT) ? BITMAP_WRAP : BITMAP_CLAMP;
m_bitmap_info = bi;
m_bitmap_matrix = m;
m_bitmap_color_transform = color_transform;
m_bitmap_color_transform.clamp();
m_color = gnash::rgba(
Uint8(m_bitmap_color_transform.m_[0][0] * 255.0f),
Uint8(m_bitmap_color_transform.m_[1][0] * 255.0f),
Uint8(m_bitmap_color_transform.m_[2][0] * 255.0f),
Uint8(m_bitmap_color_transform.m_[3][0] * 255.0f));
if (m_bitmap_color_transform.m_[0][1] > 1.0f
|| m_bitmap_color_transform.m_[1][1] > 1.0f
|| m_bitmap_color_transform.m_[2][1] > 1.0f
|| m_bitmap_color_transform.m_[3][1] > 1.0f)
{
m_has_nonzero_bitmap_additive_color = true;
}
else
{
m_has_nonzero_bitmap_additive_color = false;
}
}
bool is_valid() const { return m_mode != INVALID; }
};
// Style state.
enum style_index
{
LEFT_STYLE = 0,
RIGHT_STYLE,
LINE_STYLE,
STYLE_COUNT
};
fill_style m_current_styles[STYLE_COUNT];
gnash::bitmap_info* create_bitmap_info_rgb(image::rgb* im)
// Given an image, returns a pointer to a bitmap_info struct
// that can later be passed to fill_styleX_bitmap(), to set a
// bitmap fill style.
{
return new bitmap_info_cairo(im);
}
gnash::bitmap_info* create_bitmap_info_rgba(image::rgba* im)
// Given an image, returns a pointer to a bitmap_info struct
// that can later be passed to fill_style_bitmap(), to set a
// bitmap fill style.
//
// This version takes an image with an alpha channel.
{
return new bitmap_info_cairo(im);
}
gnash::bitmap_info* create_bitmap_info_empty()
// Create a placeholder bitmap_info. Used when
// DO_NOT_LOAD_BITMAPS is set; then later on the host program
// can use movie_definition::get_bitmap_info_count() and
// movie_definition::get_bitmap_info() to stuff precomputed
// textures into these bitmap infos.
{
return new bitmap_info_cairo;
}
gnash::bitmap_info* create_bitmap_info_alpha(int w, int h, Uint8* data)
// Create a bitmap_info so that it contains an alpha texture
// with the given data (1 byte per texel).
{
return new bitmap_info_cairo(w, h, data);
}
void delete_bitmap_info(gnash::bitmap_info* bi)
// Delete the given bitmap info struct.
{
delete bi;
}
// Constructor
render_handler_cairo() :
m_cr_offscreen(0), m_cr_mask(0), m_view_width(0), m_view_height(0)
{
}
// Destructor
~render_handler_cairo()
{
if (m_cr_mask) cairo_destroy(m_cr_mask);
if (m_cr_offscreen) cairo_destroy(m_cr_offscreen);
if (g_cr == m_cr_offscreen) g_cr = 0;
}
void begin_display(
gnash::rgba background_color,
int viewport_x0, int viewport_y0,
int viewport_width, int viewport_height,
float x0, float x1, float y0, float y1)
// Set up to render a full frame from a movie and fills the
// background. Sets up necessary transforms, to scale the
// movie to fit within the given dimensions. Call
// end_display() when you're done.
//
// The rectangle (viewport_x0, viewport_y0, viewport_x0 +
// viewport_width, viewport_y0 + viewport_height) defines the
// window coordinates taken up by the movie.
//
// The rectangle (x0, y0, x1, y1) defines the pixel
// coordinates of the movie that correspond to the viewport
// bounds.
{
// GNASH_REPORT_FUNCTION;
m_display_width = fabsf(x1 - x0);
m_display_height = fabsf(y1 - y0);
m_view_width = viewport_width;
m_view_height = viewport_height;
// Destroy offscreen surface if size is different
if (m_cr_offscreen)
{
if (m_view_width != viewport_width ||
m_view_height != viewport_height)
{
cairo_destroy(m_cr_offscreen);
m_cr_offscreen = 0;
}
}
// Create offscreen surface if necessary
if (!m_cr_offscreen)
{
cairo_surface_t* offscreen = cairo_image_surface_create(
CAIRO_FORMAT_RGB24, viewport_width, viewport_height);
m_cr_offscreen = cairo_create(offscreen);
cairo_surface_destroy(offscreen);
g_cr = m_cr_offscreen;
}
cairo_identity_matrix(g_cr);
cairo_scale(g_cr, viewport_width / m_display_width,
viewport_height / m_display_height);
cairo_translate(g_cr, x0, y0);
cairo_rectangle(g_cr, x0, y0, m_display_width, m_display_height);
cairo_clip(g_cr);
// Clear the background, if background color has alpha > 0.
if (background_color.m_a > 0)
{
// Draw a big quad.
apply_color(background_color);
cairo_rectangle(g_cr, x0, y0, x1, y1);
cairo_fill(g_cr);
}
}
void end_display()
// Clean up after rendering a frame. Client program is still
// responsible for calling glSwapBuffers() or whatever.
{
// GNASH_REPORT_FUNCTION;
// Setup output window
if (!g_cr_win)
{
Display* xdisp = XOpenDisplay(0);
Screen* screen = XDefaultScreenOfDisplay(xdisp);
Visual* visual = DefaultVisual(xdisp, DefaultScreen(xdisp));
int screen_num = XScreenNumberOfScreen(screen);
Window xwin = XCreateWindow(xdisp,
RootWindow(xdisp, screen_num),
0, 0, m_view_width, m_view_height, 0,
CopyFromParent, CopyFromParent, CopyFromParent, 0, 0);
XMapWindow(xdisp, xwin);
XRaiseWindow(xdisp, xwin);
XSync(xdisp, False);
cairo_surface_t* surface = cairo_xlib_surface_create(
xdisp, xwin, visual, m_view_width, m_view_height);
g_cr_win = cairo_create(surface);
}
// Blit offscreen image onto output window
cairo_surface_t* offscreen = cairo_get_target(m_cr_offscreen);
cairo_set_source_surface(g_cr_win, offscreen, 0, 0);
cairo_paint(g_cr_win);
}
void set_matrix(const gnash::matrix& m)
// Set the current transform for mesh & line-strip rendering.
{
m_current_matrix = m;
}
void set_cxform(const gnash::cxform& cx)
// Set the current color transform for mesh & line-strip rendering.
{
m_current_cxform = cx;
}
static void apply_matrix(const gnash::matrix& m)
// add user space transformation
{
cairo_matrix_t mat;
cairo_matrix_init(&mat, m.m_[0][0], m.m_[1][0], m.m_[0][1],
m.m_[1][1], m.m_[0][2], m.m_[1][2]);
cairo_transform(g_cr, &mat);
}
static void apply_color(const gnash::rgba& c)
// Set the given color.
{
cairo_set_source_rgba(g_cr,
c.m_r / 255.0, c.m_g / 255.0, c.m_b / 255.0, c.m_a / 255.0);
}
void fill_style_disable(int fill_side)
// Don't fill on the {0 == left, 1 == right} side of a path.
{
assert(fill_side >= 0 && fill_side < 2);
m_current_styles[fill_side].disable();
}
void line_style_disable()
// Don't draw a line on this path.
{
m_current_styles[LINE_STYLE].disable();
}
void fill_style_color(int fill_side, gnash::rgba color)
// Set fill style for the left interior of the shape. If
// enable is false, turn off fill for the left interior.
{
assert(fill_side >= 0 && fill_side < 2);
m_current_styles[fill_side].set_color(m_current_cxform.transform(color));
}
void line_style_color(gnash::rgba color)
// Set the line style of the shape. If enable is false, turn
// off lines for following curve segments.
{
m_current_styles[LINE_STYLE].set_color(m_current_cxform.transform(color));
}
void fill_style_bitmap(int fill_side, const gnash::bitmap_info* bi,
const gnash::matrix& m, bitmap_wrap_mode wm)
{
assert(fill_side >= 0 && fill_side < 2);
m_current_styles[fill_side].set_bitmap(bi, m, wm, m_current_cxform);
}
void line_style_width(float width)
{
cairo_set_line_width(g_cr, width);
}
void draw_mesh_strip(const void* coords, int vertex_count)
{
// GNASH_REPORT_FUNCTION;
// Set up current style.
m_current_styles[LEFT_STYLE].apply();
cairo_save(g_cr);
apply_matrix(m_current_matrix);
// Draw the tris in cairo
Sint16* vertex = (Sint16*)coords;
for (; vertex_count > 2; vertex_count--, vertex += 2)
{
cairo_move_to(g_cr, vertex[0], vertex[1]);
cairo_line_to(g_cr, vertex[2], vertex[3]);
cairo_line_to(g_cr, vertex[4], vertex[5]);
}
cairo_surface_t* mask = 0;
if (m_cr_mask) mask = cairo_get_target(m_cr_mask);
if (mask) cairo_mask_surface(g_cr, mask, 0, 0);
else cairo_fill(g_cr);
if (m_current_styles[LEFT_STYLE].needs_second_pass())
{
m_current_styles[LEFT_STYLE].apply_second_pass();
vertex = (Sint16*)coords;
for (; vertex_count > 2; vertex_count--, vertex += 2)
{
cairo_move_to(g_cr, vertex[0], vertex[1]);
cairo_line_to(g_cr, vertex[2], vertex[3]);
cairo_line_to(g_cr, vertex[4], vertex[5]);
}
if (mask) cairo_mask_surface(g_cr, mask, 0, 0);
else cairo_fill(g_cr);
m_current_styles[LEFT_STYLE].cleanup_second_pass();
}
cairo_restore(g_cr);
}
void draw_line_strip(const void* coords, int vertex_count)
// Draw the line strip formed by the sequence of points.
{
// GNASH_REPORT_FUNCTION;
// Set up current style.
m_current_styles[LINE_STYLE].apply();
cairo_save(g_cr);
apply_matrix(m_current_matrix);
// Draw the line-strip in cairo
Sint16* vertex = (Sint16*)coords;
cairo_move_to(g_cr, vertex[0], vertex[1]);
for (vertex += 2; vertex_count > 1; vertex_count--, vertex += 2)
cairo_line_to(g_cr, vertex[0], vertex[1]);
cairo_stroke(g_cr);
cairo_restore(g_cr);
}
void draw_bitmap(
const gnash::matrix& m,
const gnash::bitmap_info* bi,
const gnash::rect& coords,
const gnash::rect& uv_coords,
gnash::rgba color)
// Draw a rectangle textured with the given bitmap, with the
// given color. Apply given transform; ignore any currently
// set transforms.
//
// Intended for textured glyph rendering.
{
// GNASH_REPORT_FUNCTION;
assert(bi);
apply_color(color);
gnash::point a, b, c, d;
m.transform(&a, gnash::point(coords.m_x_min, coords.m_y_min));
m.transform(&b, gnash::point(coords.m_x_max, coords.m_y_min));
m.transform(&c, gnash::point(coords.m_x_min, coords.m_y_max));
d.m_x = b.m_x + c.m_x - a.m_x;
d.m_y = b.m_y + c.m_y - a.m_y;
// FIXME!!! scaling and offset is wrong
cairo_matrix_t mat;
cairo_matrix_init_scale(&mat, coords.m_x_max - coords.m_x_min,
coords.m_y_max - coords.m_y_min);
cairo_matrix_init_translate(&mat, coords.m_x_min, coords.m_y_min);
cairo_matrix_t new_mat;
cairo_matrix_init(&new_mat, m.m_[0][0], m.m_[1][0], m.m_[0][1],
m.m_[1][1], m.m_[0][2], m.m_[1][2]);
cairo_matrix_multiply(&mat, &mat, &new_mat);
cairo_pattern_t* pattern =
(cairo_pattern_t*)((bitmap_info_cairo*)bi)->m_texture_id;
cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT);
cairo_pattern_set_matrix(pattern, &mat);
cairo_set_source(g_cr, pattern);
cairo_save(g_cr);
cairo_move_to(g_cr, a.m_x, a.m_y);
cairo_line_to(g_cr, b.m_x, b.m_y);
cairo_line_to(g_cr, c.m_x, c.m_y);
cairo_line_to(g_cr, d.m_x, d.m_y);
cairo_clip(g_cr);
cairo_paint(g_cr);
cairo_restore(g_cr);
}
void begin_submit_mask()
{
// GNASH_REPORT_FUNCTION;
if (m_cr_mask) cairo_destroy(m_cr_mask);
cairo_surface_t* mask = cairo_image_surface_create(
CAIRO_FORMAT_A8, m_view_width, m_view_height);
m_cr_mask = cairo_create(mask);
cairo_surface_destroy(mask);
// Start drawing to the mask
g_cr = m_cr_mask;
}
void end_submit_mask()
{
// Finished the mask. Now draw to offscreen buffer
g_cr = m_cr_offscreen;
}
void disable_mask()
{
// Clean up any mask
if (m_cr_mask)
{
cairo_destroy(m_cr_mask);
m_cr_mask = 0;
g_cr = m_cr_offscreen;
}
}
}; // end struct render_handler_cairo
// bitmap_info_cairo implementation
bitmap_info_cairo::bitmap_info_cairo()
// Make a placeholder bitmap_info. Must be filled in later before using.
{
// GNASH_REPORT_FUNCTION;
m_buffer = 0;
m_image = 0;
m_texture_id = 0;
m_original_width = 0;
m_original_height = 0;
}
bitmap_info_cairo::bitmap_info_cairo(int width, int height, Uint8* data)
// Initialize this bitmap_info to an alpha image
// containing the specified data (1 byte per texel).
{
// GNASH_REPORT_FUNCTION;
assert(width > 0);
assert(height > 0);
assert(data);
// Allocate output buffer
int buf_size = width * height;
m_buffer = new unsigned char[buf_size];
// Copy alpha data
memcpy(m_buffer, data, buf_size);
// Create the image
m_original_width = width;
m_original_height = height;
m_image = cairo_image_surface_create_for_data(
m_buffer, CAIRO_FORMAT_A8, width, height, width);
m_texture_id = (unsigned int)cairo_pattern_create_for_surface(m_image);
}
bitmap_info_cairo::bitmap_info_cairo(image::rgb* im)
// Version of the constructor that takes an RGB image.
{
// GNASH_REPORT_FUNCTION;
assert(im);
// Allocate output buffer
int buf_size = im->m_width * im->m_height * 4;
m_buffer = new unsigned char[buf_size];
// Convert 24-bit BGR data to 32-bit RGB
unsigned char* dst = m_buffer;
for (int y = 0; y < im->m_height; y++)
{
Uint8* src = image::scanline(im, y);
for (int x = 0; x < im->m_width; x++, src += 3)
{
*dst++ = src[2]; // blue
*dst++ = src[1]; // green
*dst++ = src[0]; // red
*dst++; // alpha not used
}
}
// Create the image
m_original_width = im->m_width;
m_original_height = im->m_height;
m_image = cairo_image_surface_create_for_data(m_buffer,
CAIRO_FORMAT_RGB24, im->m_width, im->m_height, im->m_width * 4);
m_texture_id = (unsigned int)cairo_pattern_create_for_surface(m_image);
}
bitmap_info_cairo::bitmap_info_cairo(image::rgba* im)
// Version of the constructor that takes an image with alpha.
{
// GNASH_REPORT_FUNCTION;
assert(im);
// Allocate output buffer
int buf_size = im->m_width * im->m_height * 4;
m_buffer = new unsigned char[buf_size];
// Convert BGRA data to ARGB
unsigned char* dst = m_buffer;
for (int y = 0; y < im->m_height; y++)
{
Uint8* src = image::scanline(im, y);
for (int x = 0; x < im->m_width; x++, src += 4)
{
*dst++ = src[3]; // blue
*dst++ = src[2]; // green
*dst++ = src[1]; // red
*dst++ = src[0]; // alpha
}
}
// Create the image
m_original_width = im->m_width;
m_original_height = im->m_height;
m_image = cairo_image_surface_create_for_data(m_buffer,
CAIRO_FORMAT_ARGB32, im->m_width, im->m_height, im->m_width * 4);
m_texture_id = (unsigned int)cairo_pattern_create_for_surface(m_image);
}
gnash::render_handler* gnash::create_render_handler_cairo()
// Factory.
{
// GNASH_REPORT_FUNCTION;
return new render_handler_cairo;
}
// Local Variables:
// mode: C++
// indent-tabs-mode: t
// End:
/* vim: set cindent tabstop=8 softtabstop=4 shiftwidth=4: */