diff --git a/gdk/broadway/broadway-protocol.h b/gdk/broadway/broadway-protocol.h index 68911add81..70c5c18522 100644 --- a/gdk/broadway/broadway-protocol.h +++ b/gdk/broadway/broadway-protocol.h @@ -22,6 +22,9 @@ typedef enum { /* Sync changes with broadway.js */ BROADWAY_NODE_CLIP = 10, BROADWAY_NODE_KEEP_ALL = 11, BROADWAY_NODE_KEEP_THIS = 12, + BROADWAY_NODE_TRANSLATE = 13, + BROADWAY_NODE_DEBUG = 14, + BROADWAY_NODE_REUSE = 15, } BroadwayNodeType; static const char *broadway_node_type_names[] G_GNUC_UNUSED = { @@ -38,6 +41,8 @@ static const char *broadway_node_type_names[] G_GNUC_UNUSED = { "CLIP", "KEEP_ALL", "KEEP_THIS", + "TRANSLATE", + "DEBUG", }; typedef enum { diff --git a/gdk/broadway/broadway-server.c b/gdk/broadway/broadway-server.c index 951c7035c1..ac9c424cf9 100644 --- a/gdk/broadway/broadway-server.c +++ b/gdk/broadway/broadway-server.c @@ -35,7 +35,6 @@ #include #endif - typedef struct { int id; guint32 tag; @@ -126,23 +125,56 @@ struct BroadwaySurface { gint32 transient_for; guint32 texture; BroadwayNode *nodes; + GHashTable *node_lookup; +}; + +struct _BroadwayTexture { + grefcount refcount; + guint32 id; + GBytes *bytes; }; static void broadway_server_resync_surfaces (BroadwayServer *server); static void send_outstanding_roundtrips (BroadwayServer *server); +static void broadway_server_ref_texture (BroadwayServer *server, + guint32 id); + static GType broadway_server_get_type (void); G_DEFINE_TYPE (BroadwayServer, broadway_server, G_TYPE_OBJECT) static void -broadway_node_free (BroadwayNode *node) +broadway_texture_free (BroadwayTexture *texture) +{ + g_bytes_unref (texture->bytes); + g_free (texture); +} + +static void +broadway_node_unref (BroadwayServer *server, + BroadwayNode *node) { int i; - for (i = 0; i < node->n_children; i++) - broadway_node_free (node->children[i]); - g_free (node); + if (g_ref_count_dec (&node->refcount)) + { + for (i = 0; i < node->n_children; i++) + broadway_node_unref (server, node->children[i]); + + if (node->texture_id) + broadway_server_release_texture (server, node->texture_id); + + g_free (node); + } +} + +static BroadwayNode * +broadway_node_ref (BroadwayNode *node) +{ + g_ref_count_inc (&node->refcount); + + return node; } gboolean @@ -204,7 +236,7 @@ broadway_server_init (BroadwayServer *server) server->surface_id_hash = g_hash_table_new (NULL, NULL); server->id_counter = 0; server->textures = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, - (GDestroyNotify)g_bytes_unref); + (GDestroyNotify)broadway_texture_free); root = g_new0 (BroadwaySurface, 1); root->id = server->id_counter++; @@ -241,10 +273,12 @@ broadway_server_class_init (BroadwayServerClass * class) } static void -broadway_surface_free (BroadwaySurface *surface) +broadway_surface_free (BroadwayServer *server, + BroadwaySurface *surface) { if (surface->nodes) - broadway_node_free (surface->nodes); + broadway_node_unref (server, surface->nodes); + g_hash_table_unref (surface->node_lookup); g_free (surface); } @@ -1477,7 +1511,7 @@ broadway_server_destroy_surface (BroadwayServer *server, server->surfaces = g_list_remove (server->surfaces, surface); g_hash_table_remove (server->surface_id_hash, GINT_TO_POINTER (id)); - broadway_surface_free (surface); + broadway_surface_free (server, surface); } } @@ -1605,53 +1639,232 @@ broadway_server_has_client (BroadwayServer *server) return server->output != NULL; } +#define NODE_SIZE_COLOR 1 +#define NODE_SIZE_FLOAT 1 +#define NODE_SIZE_POINT 2 +#define NODE_SIZE_SIZE 2 +#define NODE_SIZE_RECT (NODE_SIZE_POINT + NODE_SIZE_SIZE) +#define NODE_SIZE_RRECT (NODE_SIZE_RECT + 4 * NODE_SIZE_SIZE) +#define NODE_SIZE_COLOR_STOP (NODE_SIZE_FLOAT + NODE_SIZE_COLOR) +#define NODE_SIZE_SHADOW (NODE_SIZE_COLOR + 3 * NODE_SIZE_FLOAT) + +static guint32 +rotl (guint32 value, int shift) +{ + if ((shift &= 32 - 1) == 0) + return value; + return (value << shift) | (value >> (32 - shift)); +} + +static BroadwayNode * +decode_nodes (BroadwayServer *server, + BroadwaySurface *surface, + int len, + guint32 data[], + GHashTable *client_texture_map, + int *pos) +{ + BroadwayNode *node; + guint32 type, id; + guint32 i, n_stops, n_shadows, n_chars; + guint32 size, n_children; + gint32 texture_offset; + guint32 hash; + + g_assert (*pos < len); + + size = 0; + n_children = 0; + texture_offset = -1; + + type = data[(*pos)++]; + id = data[(*pos)++]; + switch (type) { + case BROADWAY_NODE_REUSE: + node = g_hash_table_lookup (surface->node_lookup, GINT_TO_POINTER(id)); + g_assert (node != NULL); + return broadway_node_ref (node); + break; + case BROADWAY_NODE_COLOR: + size = NODE_SIZE_RECT + NODE_SIZE_COLOR; + break; + case BROADWAY_NODE_BORDER: + size = NODE_SIZE_RRECT + 4 * NODE_SIZE_FLOAT + 4 * NODE_SIZE_COLOR; + break; + case BROADWAY_NODE_INSET_SHADOW: + case BROADWAY_NODE_OUTSET_SHADOW: + size = NODE_SIZE_RRECT + NODE_SIZE_COLOR + 4 * NODE_SIZE_FLOAT; + break; + case BROADWAY_NODE_TEXTURE: + texture_offset = 4; + size = 5; + break; + case BROADWAY_NODE_CONTAINER: + size = 1; + n_children = data[*pos]; + break; + case BROADWAY_NODE_ROUNDED_CLIP: + size = NODE_SIZE_RRECT; + n_children = 1; + break; + case BROADWAY_NODE_CLIP: + size = NODE_SIZE_RECT; + n_children = 1; + break; + case BROADWAY_NODE_TRANSLATE: + size = NODE_SIZE_POINT; + n_children = 1; + break; + case BROADWAY_NODE_LINEAR_GRADIENT: + size = NODE_SIZE_RECT + 2 * NODE_SIZE_POINT; + n_stops = data[*pos + size++]; + size += n_stops * NODE_SIZE_COLOR_STOP; + break; + case BROADWAY_NODE_SHADOW: + size = 1; + n_shadows = data[*pos]; + size += n_shadows * NODE_SIZE_SHADOW; + n_children = 1; + break; + case BROADWAY_NODE_OPACITY: + size = NODE_SIZE_FLOAT; + n_children = 1; + break; + case BROADWAY_NODE_DEBUG: + n_chars = data[*pos]; + size = 1 + (n_chars + 3) / 4; + n_children = 1; + break; + default: + g_assert_not_reached (); + } + + node = g_malloc (sizeof(BroadwayNode) + (size - 1) * sizeof(guint32) + n_children * sizeof (BroadwayNode *)); + g_ref_count_init (&node->refcount); + node->type = type; + node->id = id; + node->texture_id = 0; + node->n_children = n_children; + node->children = (BroadwayNode **)((char *)node + sizeof(BroadwayNode) + (size - 1) * sizeof(guint32)); + node->n_data = size; + for (i = 0; i < size; i++) + { + node->data[i] = data[(*pos)++]; + if (i == texture_offset) + { + node->texture_id = GPOINTER_TO_INT (g_hash_table_lookup (client_texture_map, GINT_TO_POINTER (node->data[i]))); + broadway_server_ref_texture (server, node->texture_id); + node->data[i] = node->texture_id; + } + } + + for (i = 0; i < n_children; i++) + node->children[i] = decode_nodes (server, surface, len, data, client_texture_map, pos); + + hash = node->type << 16; + + for (i = 0; i < size; i++) + hash ^= rotl (node->data[i], i); + + for (i = 0; i < n_children; i++) + hash ^= rotl (node->children[i]->hash, i); + + node->hash = hash; + + return node; +} + +static void +init_node_lookup (BroadwaySurface *surface, + BroadwayNode *node) +{ + int i; + + g_hash_table_insert (surface->node_lookup, GINT_TO_POINTER(node->id), node); + for (i = 0; i < node->n_children; i++) + init_node_lookup (surface, node->children[i]); +} + /* passes ownership of nodes */ void -broadway_server_surface_set_nodes (BroadwayServer *server, - gint id, - BroadwayNode *root) +broadway_server_surface_update_nodes (BroadwayServer *server, + gint id, + guint32 data[], + int len, + GHashTable *client_texture_map) { BroadwaySurface *surface; + int pos = 0; + BroadwayNode *root; surface = broadway_server_lookup_surface (server, id); if (surface == NULL) return; + root = decode_nodes (server, surface, len, data, client_texture_map, &pos); + if (server->output != NULL) broadway_output_surface_set_nodes (server->output, surface->id, root, surface->nodes); if (surface->nodes) - broadway_node_free (surface->nodes); + broadway_node_unref (server, surface->nodes); + surface->nodes = root; + + g_hash_table_remove_all (surface->node_lookup); + + init_node_lookup (surface, surface->nodes); } guint32 broadway_server_upload_texture (BroadwayServer *server, - GBytes *texture) + GBytes *bytes) { - guint32 id; + BroadwayTexture *texture; + + texture = g_new0 (BroadwayTexture, 1); + g_ref_count_init (&texture->refcount); + texture->id = ++server->next_texture_id; + texture->bytes = g_bytes_ref (bytes); - id = ++server->next_texture_id; g_hash_table_replace (server->textures, - GINT_TO_POINTER (id), - g_bytes_ref (texture)); + GINT_TO_POINTER (texture->id), + texture); if (server->output) - broadway_output_upload_texture (server->output, id, texture); + broadway_output_upload_texture (server->output, texture->id, texture->bytes); - return id; + return texture->id; +} + +static void +broadway_server_ref_texture (BroadwayServer *server, + guint32 id) +{ + BroadwayTexture *texture; + + texture = g_hash_table_lookup (server->textures, GINT_TO_POINTER (id)); + if (texture) + g_ref_count_inc (&texture->refcount); } void broadway_server_release_texture (BroadwayServer *server, guint32 id) { - g_hash_table_remove (server->textures, GINT_TO_POINTER (id)); + BroadwayTexture *texture; - if (server->output) - broadway_output_release_texture (server->output, id); + texture = g_hash_table_lookup (server->textures, GINT_TO_POINTER (id)); + + if (texture && g_ref_count_dec (&texture->refcount)) + { + g_hash_table_remove (server->textures, GINT_TO_POINTER (id)); + + if (server->output) + broadway_output_release_texture (server->output, id); + } } gboolean @@ -1801,6 +2014,7 @@ broadway_server_new_surface (BroadwayServer *server, surface->width = width; surface->height = height; surface->is_temp = is_temp; + surface->node_lookup = g_hash_table_new (g_direct_hash, g_direct_equal); g_hash_table_insert (server->surface_id_hash, GINT_TO_POINTER (surface->id), @@ -1835,9 +2049,12 @@ broadway_server_resync_surfaces (BroadwayServer *server) /* First upload all textures */ g_hash_table_iter_init (&iter, server->textures); while (g_hash_table_iter_next (&iter, &key, &value)) - broadway_output_upload_texture (server->output, - GPOINTER_TO_INT (key), - (GBytes *)value); + { + BroadwayTexture *texture = value; + broadway_output_upload_texture (server->output, + GPOINTER_TO_INT (key), + texture->bytes); + } /* Then create all surfaces */ for (l = server->surfaces; l != NULL; l = l->next) diff --git a/gdk/broadway/broadway-server.h b/gdk/broadway/broadway-server.h index 73ec89e051..1e125bd065 100644 --- a/gdk/broadway/broadway-server.h +++ b/gdk/broadway/broadway-server.h @@ -19,12 +19,16 @@ typedef struct _BroadwayServerClass BroadwayServerClass; #define BROADWAY_SERVER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), BROADWAY_TYPE_SERVER, BroadwayServerClass)) typedef struct _BroadwayNode BroadwayNode; +typedef struct _BroadwayTexture BroadwayTexture; struct _BroadwayNode { + grefcount refcount; guint32 type; + guint32 id; guint32 hash; /* deep hash */ guint32 n_children; BroadwayNode **children; + guint32 texture_id; guint32 n_data; guint32 data[1]; }; @@ -99,9 +103,11 @@ void broadway_server_release_texture (BroadwayServer * guint32 id); cairo_surface_t * broadway_server_create_surface (int width, int height); -void broadway_server_surface_set_nodes (BroadwayServer *server, +void broadway_server_surface_update_nodes (BroadwayServer *server, gint id, - BroadwayNode *root); + guint32 data[], + int len, + GHashTable *client_texture_map); gboolean broadway_server_surface_move_resize (BroadwayServer *server, gint id, gboolean with_move, diff --git a/gdk/broadway/broadway.js b/gdk/broadway/broadway.js index 24859f1b15..00c341c393 100644 --- a/gdk/broadway/broadway.js +++ b/gdk/broadway/broadway.js @@ -1,3 +1,94 @@ +// Protocol stuff + +const BROADWAY_NODE_TEXTURE = 0; +const BROADWAY_NODE_CONTAINER = 1; +const BROADWAY_NODE_COLOR = 2; +const BROADWAY_NODE_BORDER = 3; +const BROADWAY_NODE_OUTSET_SHADOW = 4; +const BROADWAY_NODE_INSET_SHADOW = 5; +const BROADWAY_NODE_ROUNDED_CLIP = 6; +const BROADWAY_NODE_LINEAR_GRADIENT = 7; +const BROADWAY_NODE_SHADOW = 8; +const BROADWAY_NODE_OPACITY = 9; +const BROADWAY_NODE_CLIP = 10; +const BROADWAY_NODE_KEEP_ALL = 11; +const BROADWAY_NODE_KEEP_THIS = 12; +const BROADWAY_NODE_TRANSLATE = 13; +const BROADWAY_NODE_DEBUG = 14; +const BROADWAY_NODE_REUSE = 15; + +const BROADWAY_OP_GRAB_POINTER = 'g'; +const BROADWAY_OP_UNGRAB_POINTER = 'u'; +const BROADWAY_OP_NEW_SURFACE = 's'; +const BROADWAY_OP_SHOW_SURFACE = 'S'; +const BROADWAY_OP_HIDE_SURFACE = 'H'; +const BROADWAY_OP_RAISE_SURFACE = 'r'; +const BROADWAY_OP_LOWER_SURFACE = 'R'; +const BROADWAY_OP_DESTROY_SURFACE = 'd'; +const BROADWAY_OP_MOVE_RESIZE = 'm'; +const BROADWAY_OP_SET_TRANSIENT_FOR = 'p'; +const BROADWAY_OP_PUT_RGB = 'i'; +const BROADWAY_OP_REQUEST_AUTH = 'l'; +const BROADWAY_OP_AUTH_OK = 'L'; +const BROADWAY_OP_DISCONNECTED = 'D'; +const BROADWAY_OP_SURFACE_UPDATE = 'b'; +const BROADWAY_OP_SET_SHOW_KEYBOARD = 'k'; +const BROADWAY_OP_UPLOAD_TEXTURE = 't'; +const BROADWAY_OP_RELEASE_TEXTURE = 'T'; +const BROADWAY_OP_SET_NODES = 'n'; +const BROADWAY_OP_ROUNDTRIP = 'F'; + +const BROADWAY_EVENT_ENTER = 'e'; +const BROADWAY_EVENT_LEAVE = 'l'; +const BROADWAY_EVENT_POINTER_MOVE = 'm'; +const BROADWAY_EVENT_BUTTON_PRESS = 'b'; +const BROADWAY_EVENT_BUTTON_RELEASE = 'B'; +const BROADWAY_EVENT_TOUCH = 't'; +const BROADWAY_EVENT_SCROLL = 's'; +const BROADWAY_EVENT_KEY_PRESS = 'k'; +const BROADWAY_EVENT_KEY_RELEASE = 'K'; +const BROADWAY_EVENT_GRAB_NOTIFY = 'g'; +const BROADWAY_EVENT_UNGRAB_NOTIFY = 'u'; +const BROADWAY_EVENT_CONFIGURE_NOTIFY = 'w'; +const BROADWAY_EVENT_SCREEN_SIZE_CHANGED = 'd'; +const BROADWAY_EVENT_FOCUS = 'f'; +const BROADWAY_EVENT_ROUNDTRIP_NOTIFY = 'F'; + +const DISPLAY_OP_REPLACE_CHILD = 0; +const DISPLAY_OP_APPEND_CHILD = 1; +const DISPLAY_OP_APPEND_ROOT = 2; +const DISPLAY_OP_SHOW_SURFACE = 3; +const DISPLAY_OP_HIDE_SURFACE = 4; +const DISPLAY_OP_DELETE_NODE = 5; +const DISPLAY_OP_MOVE_NODE = 6; +const DISPLAY_OP_RESIZE_NODE = 7; +const DISPLAY_OP_RESTACK_SURFACES = 8; +const DISPLAY_OP_DELETE_SURFACE = 9; + +// GdkCrossingMode +const GDK_CROSSING_NORMAL = 0; +const GDK_CROSSING_GRAB = 1; +const GDK_CROSSING_UNGRAB = 2; + +// GdkModifierType +const GDK_SHIFT_MASK = 1 << 0; +const GDK_LOCK_MASK = 1 << 1; +const GDK_CONTROL_MASK = 1 << 2; +const GDK_MOD1_MASK = 1 << 3; +const GDK_MOD2_MASK = 1 << 4; +const GDK_MOD3_MASK = 1 << 5; +const GDK_MOD4_MASK = 1 << 6; +const GDK_MOD5_MASK = 1 << 7; +const GDK_BUTTON1_MASK = 1 << 8; +const GDK_BUTTON2_MASK = 1 << 9; +const GDK_BUTTON3_MASK = 1 << 10; +const GDK_BUTTON4_MASK = 1 << 11; +const GDK_BUTTON5_MASK = 1 << 12; +const GDK_SUPER_MASK = 1 << 26; +const GDK_HYPER_MASK = 1 << 27; +const GDK_META_MASK = 1 << 28; +const GDK_RELEASE_MASK = 1 << 30; + /* Helper functions for debugging */ var logDiv = null; function log(str) { @@ -84,6 +175,7 @@ var surfaces = {}; var textures = {}; var stackingOrder = []; var outstandingCommands = new Array(); +var outstandingDisplayCommands = null; var inputSocket = null; var debugDecoding = false; var fakeInput = null; @@ -91,29 +183,6 @@ var showKeyboard = false; var showKeyboardChanged = false; var firstTouchDownId = null; -var GDK_CROSSING_NORMAL = 0; -var GDK_CROSSING_GRAB = 1; -var GDK_CROSSING_UNGRAB = 2; - -// GdkModifierType -var GDK_SHIFT_MASK = 1 << 0; -var GDK_LOCK_MASK = 1 << 1; -var GDK_CONTROL_MASK = 1 << 2; -var GDK_MOD1_MASK = 1 << 3; -var GDK_MOD2_MASK = 1 << 4; -var GDK_MOD3_MASK = 1 << 5; -var GDK_MOD4_MASK = 1 << 6; -var GDK_MOD5_MASK = 1 << 7; -var GDK_BUTTON1_MASK = 1 << 8; -var GDK_BUTTON2_MASK = 1 << 9; -var GDK_BUTTON3_MASK = 1 << 10; -var GDK_BUTTON4_MASK = 1 << 11; -var GDK_BUTTON5_MASK = 1 << 12; -var GDK_SUPER_MASK = 1 << 26; -var GDK_HYPER_MASK = 1 << 27; -var GDK_META_MASK = 1 << 28; -var GDK_RELEASE_MASK = 1 << 30; - function getButtonMask (button) { if (button == 1) return GDK_BUTTON1_MASK; @@ -128,12 +197,36 @@ function getButtonMask (button) { return 0; } -function sendConfigureNotify(surface) -{ - sendInput("w", [surface.id, surface.x, surface.y, surface.width, surface.height]); +function Texture(id, data) { + var blob = new Blob([data],{type: "image/png"}); + this.url = window.URL.createObjectURL(blob); + this.refcount = 1; + this.id = id; + + var image = new Image(); + image.src = this.url; + this.image = image; + textures[id] = this; +} + +Texture.prototype.ref = function() { + this.refcount += 1; + return this; +} + +Texture.prototype.unref = function() { + this.refcount -= 1; + if (this.refcount == 0) { + window.URL.revokeObjectURL(this.url); + delete textures[this.id]; + } +} + +function sendConfigureNotify(surface) +{ + sendInput(BROADWAY_EVENT_CONFIGURE_NOTIFY, [surface.id, surface.x, surface.y, surface.width, surface.height]); } -var positionIndex = 0; function cmdCreateSurface(id, x, y, width, height, isTemp) { var surface = { id: id, x: x, y:y, width: width, height: height, isTemp: isTemp }; @@ -146,8 +239,6 @@ function cmdCreateSurface(id, x, y, width, height, isTemp) div.surface = surface; surface.div = div; - document.body.appendChild(div); - div.style["position"] = "absolute"; div.style["left"] = surface.x + "px"; div.style["top"] = surface.y + "px"; @@ -160,54 +251,8 @@ function cmdCreateSurface(id, x, y, width, height, isTemp) stackingOrder.push(surface); sendConfigureNotify(surface); -} -function cmdShowSurface(id) -{ - var surface = surfaces[id]; - - if (surface.visible) - return; - surface.visible = true; - - var xOffset = surface.x; - var yOffset = surface.y; - - surface.div.style["left"] = xOffset + "px"; - surface.div.style["top"] = yOffset + "px"; - surface.div.style["visibility"] = "visible"; - - restackSurfaces(); -} - -function cmdHideSurface(id) -{ - if (grab.surface == id) - doUngrab(); - - var surface = surfaces[id]; - if (!surface.visible) - return; - surface.visible = false; - - surface.div.style["visibility"] = "hidden"; -} - -function cmdSetTransientFor(id, parentId) -{ - var surface = surfaces[id]; - - if (surface.transientParent == parentId) - return; - - surface.transientParent = parentId; - if (parentId != 0 && surfaces[parentId]) { - moveToHelper(surface, stackingOrder.indexOf(surfaces[parentId])+1); - } - - if (surface.visible) { - restackSurfaces(); - } + return div; } function restackSurfaces() { @@ -232,88 +277,52 @@ function moveToHelper(surface, position) { } } -function cmdDeleteSurface(id) -{ - if (grab.surface == id) - doUngrab(); - - var surface = surfaces[id]; - var i = stackingOrder.indexOf(surface); - if (i >= 0) - stackingOrder.splice(i, 1); - var div = surface.div; - div.parentNode.removeChild(div); - delete surfaces[id]; -} - function cmdRoundtrip(id, tag) { - sendInput("F", [id, tag]); -} - -function cmdMoveResizeSurface(id, has_pos, x, y, has_size, w, h) -{ - var surface = surfaces[id]; - if (has_pos) { - surface.positioned = true; - surface.x = x; - surface.y = y; - } - - if (has_size) { - surface.width = w; - surface.height = h; - - surface.div.style["width"] = surface.width + "px"; - surface.div.style["height"] = surface.height + "px"; - } - - if (surface.visible) { - if (has_pos) { - var xOffset = surface.x; - var yOffset = surface.y; - - surface.div.style["left"] = xOffset + "px"; - surface.div.style["top"] = yOffset + "px"; - } - } - - sendConfigureNotify(surface); + sendInput(BROADWAY_EVENT_ROUNDTRIP_NOTIFY, [id, tag]); } function cmdRaiseSurface(id) { var surface = surfaces[id]; - - moveToHelper(surface); - restackSurfaces(); + if (surface) + moveToHelper(surface); } function cmdLowerSurface(id) { var surface = surfaces[id]; - - moveToHelper(surface, 0); - restackSurfaces(); + if (surface) + moveToHelper(surface, 0); } -function SwapNodes(node_data, div) { +function TransformNodes(node_data, div, display_commands) { this.node_data = node_data; - this.node_data_signed = new Int32Array(node_data); + this.display_commands = display_commands; this.data_pos = 0; this.div = div; this.outstanding = 1; } -SwapNodes.prototype.decode_uint32 = function() { - return this.node_data[this.data_pos++]; +TransformNodes.prototype.decode_uint32 = function() { + var v = this.node_data.getUint32(this.data_pos, true); + this.data_pos += 4; + return v; } -SwapNodes.prototype.decode_int32 = function() { - return this.node_data_signed[this.data_pos++]; +TransformNodes.prototype.decode_int32 = function() { + var v = this.node_data.getInt32(this.data_pos, true); + this.data_pos += 4; + return v; } -SwapNodes.prototype.decode_color = function() { +TransformNodes.prototype.decode_float = function() { + var v = this.node_data.getFloat32(this.data_pos, true); + this.data_pos += 4; + return v; +} + +TransformNodes.prototype.decode_color = function() { var rgba = this.decode_uint32(); var a = (rgba >> 24) & 0xff; var r = (rgba >> 16) & 0xff; @@ -327,25 +336,21 @@ SwapNodes.prototype.decode_color = function() { return c; } -SwapNodes.prototype.decode_float = function() { - return this.decode_int32() / 256.0; -} - -SwapNodes.prototype.decode_size = function() { +TransformNodes.prototype.decode_size = function() { var s = new Object(); s.width = this.decode_float (); s.height = this.decode_float (); return s; } -SwapNodes.prototype.decode_point = function() { +TransformNodes.prototype.decode_point = function() { var p = new Object(); p.x = this.decode_float (); p.y = this.decode_float (); return p; } -SwapNodes.prototype.decode_rect = function() { +TransformNodes.prototype.decode_rect = function() { var r = new Object(); r.x = this.decode_float (); r.y = this.decode_float (); @@ -354,7 +359,7 @@ SwapNodes.prototype.decode_rect = function() { return r; } -SwapNodes.prototype.decode_irect = function() { +TransformNodes.prototype.decode_irect = function() { var r = new Object(); r.x = this.decode_int32 (); r.y = this.decode_int32 (); @@ -363,7 +368,7 @@ SwapNodes.prototype.decode_irect = function() { return r; } -SwapNodes.prototype.decode_rounded_rect = function() { +TransformNodes.prototype.decode_rounded_rect = function() { var r = new Object(); r.bounds = this.decode_rect(); r.sizes = []; @@ -372,14 +377,14 @@ SwapNodes.prototype.decode_rounded_rect = function() { return r; } -SwapNodes.prototype.decode_color_stop = function() { +TransformNodes.prototype.decode_color_stop = function() { var s = new Object(); s.offset = this.decode_float (); s.color = this.decode_color (); return s; } -SwapNodes.prototype.decode_color_stops = function() { +TransformNodes.prototype.decode_color_stops = function() { var stops = []; var len = this.decode_uint32(); for (var i = 0; i < len; i++) @@ -387,6 +392,55 @@ SwapNodes.prototype.decode_color_stops = function() { return stops; } +function utf8_to_string(array) { + var out, i, len, c; + var char2, char3; + + out = ""; + len = array.length; + i = 0; + while(i < len) { + c = array[i++]; + switch(c >> 4) + { + case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: + // 0xxxxxxx + out += String.fromCharCode(c); + break; + case 12: case 13: + // 110x xxxx 10xx xxxx + char2 = array[i++]; + out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F)); + break; + case 14: + // 1110 xxxx 10xx xxxx 10xx xxxx + char2 = array[i++]; + char3 = array[i++]; + out += String.fromCharCode(((c & 0x0F) << 12) | + ((char2 & 0x3F) << 6) | + ((char3 & 0x3F) << 0)); + break; + } + } + + return out; +} + +TransformNodes.prototype.decode_string = function() { + var len = this.decode_uint32(); + var utf8 = new Array(); + var b; + for (var i = 0; i < len; i++) { + if (i % 4 == 0) { + b = this.decode_uint32(); + } + utf8[i] = b & 0xff; + b = b >> 8; + } + + return utf8_to_string (utf8); +} + function args() { var argsLength = arguments.length; var strings = []; @@ -400,6 +454,11 @@ function px(x) { return x + "px"; } +function set_point_style (div, point) { + div.style["left"] = px(point.x); + div.style["top"] = px(point.y); +} + function set_rect_style (div, rect) { div.style["left"] = px(rect.x); div.style["top"] = px(rect.y); @@ -415,7 +474,7 @@ function set_rrect_style (div, rrect) { div.style["border-bottom-left-radius"] = args(px(rrect.sizes[3].width), px(rrect.sizes[3].height)); } -SwapNodes.prototype.insertNode = function(parent, posInParent, oldNode) +TransformNodes.prototype.insertNode = function(parent, posInParent, oldNode) { var type = this.decode_uint32(); var newNode = null; @@ -431,7 +490,7 @@ SwapNodes.prototype.insertNode = function(parent, posInParent, oldNode) { /* Leaf nodes */ - case 0: // TEXTURE + case BROADWAY_NODE_TEXTURE: { var rect = this.decode_rect(); var texture_id = this.decode_uint32(); @@ -440,13 +499,15 @@ SwapNodes.prototype.insertNode = function(parent, posInParent, oldNode) image.height = rect.height; image.style["position"] = "absolute"; set_rect_style(image, rect); - var texture_url = textures[texture_id]; - image.src = texture_url; + var texture = textures[texture_id].ref(); + image.src = texture.url; + // Unref blob url when loaded + image.onload = function() { texture.unref(); }; newNode = image; } break; - case 2: // COLOR + case BROADWAY_NODE_COLOR: { var rect = this.decode_rect(); var c = this.decode_color (); @@ -458,7 +519,7 @@ SwapNodes.prototype.insertNode = function(parent, posInParent, oldNode) } break; - case 3: // BORDER + case BROADWAY_NODE_BORDER: { var rrect = this.decode_rounded_rect(); var border_widths = []; @@ -486,7 +547,7 @@ SwapNodes.prototype.insertNode = function(parent, posInParent, oldNode) } break; - case 4: // OUTSET_SHADOW + case BROADWAY_NODE_OUTSET_SHADOW: { var rrect = this.decode_rounded_rect(); var color = this.decode_color(); @@ -503,7 +564,7 @@ SwapNodes.prototype.insertNode = function(parent, posInParent, oldNode) } break; - case 5: // INSET_SHADOW + case BROADWAY_NODE_INSET_SHADOW: { var rrect = this.decode_rounded_rect(); var color = this.decode_color(); @@ -521,7 +582,7 @@ SwapNodes.prototype.insertNode = function(parent, posInParent, oldNode) break; - case 7: // LINEAR_GRADIENT + case BROADWAY_NODE_LINEAR_GRADIENT: { var rect = this.decode_rect(); var start = this.decode_point (); @@ -569,7 +630,18 @@ SwapNodes.prototype.insertNode = function(parent, posInParent, oldNode) /* Bin nodes */ - case 10: // CLIP + case BROADWAY_NODE_TRANSLATE: + { + var point = this.decode_point(); + var div = document.createElement('div'); + div.style["position"] = "absolute"; + set_point_style(div, point); + this.insertNode(div, -1, oldChildren[0]); + newNode = div; + } + break; + + case BROADWAY_NODE_CLIP: { var rect = this.decode_rect(); var div = document.createElement('div'); @@ -581,7 +653,7 @@ SwapNodes.prototype.insertNode = function(parent, posInParent, oldNode) } break; - case 6: // ROUNDED_CLIP + case BROADWAY_NODE_ROUNDED_CLIP: { var rrect = this.decode_rounded_rect(); var div = document.createElement('div'); @@ -593,7 +665,7 @@ SwapNodes.prototype.insertNode = function(parent, posInParent, oldNode) } break; - case 9: // OPACITY + case BROADWAY_NODE_OPACITY: { var opacity = this.decode_float(); var div = document.createElement('div'); @@ -607,7 +679,7 @@ SwapNodes.prototype.insertNode = function(parent, posInParent, oldNode) } break; - case 8: // SHADOW + case BROADWAY_NODE_SHADOW: { var len = this.decode_uint32(); var filters = ""; @@ -629,9 +701,19 @@ SwapNodes.prototype.insertNode = function(parent, posInParent, oldNode) } break; + case 14: // DEBUG + { + var str = this.decode_string(); + var div = document.createElement('div'); + div.setAttribute('debug', str); + this.insertNode(div, -1, oldChildren[0]); + newNode = div; + } + break; + /* Generic nodes */ - case 1: // CONTAINER + case BROADWAY_NODE_CONTAINER: { var div = document.createElement('div'); var len = this.decode_uint32(); @@ -642,7 +724,7 @@ SwapNodes.prototype.insertNode = function(parent, posInParent, oldNode) } break; - case 11: // KEEP_ALL + case BROADWAY_NODE_KEEP_ALL: { if (!oldNode) alert("KEEP_ALL with no oldNode"); @@ -654,7 +736,7 @@ SwapNodes.prototype.insertNode = function(parent, posInParent, oldNode) } break; - case 12: // KEEP_THIS + case BROADWAY_NODE_KEEP_THIS: { if (!oldNode) alert("KEEP_THIS with no oldNode "); @@ -686,168 +768,248 @@ SwapNodes.prototype.insertNode = function(parent, posInParent, oldNode) if (newNode) { if (posInParent >= 0 && parent.children[posInParent]) - parent.replaceChild(newNode, parent.children[posInParent]); + this.display_commands.push([DISPLAY_OP_REPLACE_CHILD, parent, newNode, parent.children[posInParent]]); else - parent.appendChild(newNode); + this.display_commands.push([DISPLAY_OP_APPEND_CHILD, parent, newNode]); } } -function cmdSurfaceSetNodes(id, node_data) +TransformNodes.prototype.execute = function(display_commands) { - var surface = surfaces[id]; - surface.node_data = node_data; - - var div = surface.div; - - /* We use a secondary div so that we can remove all previous children in one go */ - - var swap = new SwapNodes (node_data, div); - swap.insertNode(div, 0, div.firstChild); - if (swap.data_pos != node_data.length) - alert ("Did not consume entire array (len " + node_data.length + " end " + end + ")"); + var div = this.div; + this.insertNode(div, 0, div.firstChild, display_commands); + if (this.data_pos != this.node_data.byteLength) + alert ("Did not consume entire array (len " + this.node_data.byteLength + ")"); } -function cmdUploadTexture(id, data) -{ - var blob = new Blob([data],{type: "image/png"}); - var url = window.URL.createObjectURL(blob); - textures[id] = url; -} - -function cmdReleaseTexture(id) -{ - var url = textures[id]; - window.URL.revokeObjectURL(url); - delete textures[id]; -} function cmdGrabPointer(id, ownerEvents) { doGrab(id, ownerEvents, false); - sendInput ("g", []); + sendInput (BROADWAY_EVENT_GRAB_NOTIFY, []); } function cmdUngrabPointer() { - sendInput ("u", []); + sendInput (BROADWAY_EVENT_UNGRAB_NOTIFY, []); if (grab.surface) doUngrab(); } -var active = false; -function handleCommands(cmd) +function handleDisplayCommands(display_commands) { - if (!active) { - start(); - active = true; - } + var div; + var len = display_commands.length; + for (var i = 0; i < len; i++) { + var cmd = display_commands[i]; - while (cmd.pos < cmd.length) { - var id, x, y, w, h, q; + switch (cmd[0]) { + case DISPLAY_OP_REPLACE_CHILD: + cmd[1].replaceChild(cmd[2], cmd[3]); + break; + case DISPLAY_OP_APPEND_CHILD: + cmd[1].appendChild(cmd[2]); + break; + case DISPLAY_OP_APPEND_ROOT: + document.body.appendChild(cmd[1]); + break; + case DISPLAY_OP_SHOW_SURFACE: + div = cmd[1]; + var xOffset = cmd[2]; + var yOffset = cmd[3]; + div.style["left"] = xOffset + "px"; + div.style["top"] = yOffset + "px"; + div.style["visibility"] = "visible"; + break; + case DISPLAY_OP_HIDE_SURFACE: + div = cmd[1]; + div.style["visibility"] = "hidden"; + break; + case DISPLAY_OP_DELETE_NODE: + div = cmd[1]; + div.parentNode.removeChild(div); + break; + case DISPLAY_OP_MOVE_NODE: + div = cmd[1]; + div.style["left"] = cmd[2] + "px"; + div.style["top"] = cmd[3] + "px"; + break; + case DISPLAY_OP_RESIZE_NODE: + div = cmd[1]; + div.style["width"] = cmd[2] + "px"; + div.style["height"] = cmd[3] + "px"; + break; + + case DISPLAY_OP_RESTACK_SURFACES: + restackSurfaces(); + break; + case DISPLAY_OP_DELETE_SURFACE: + var id = cmd[1]; + delete surfaces[id]; + break; + + default: + alert("Unknown display op " + command); + } + } +} + +function handleCommands(cmd, display_commands, new_textures, modified_trees) +{ + var res = true; + var need_restack = false; + + while (res && cmd.pos < cmd.length) { + var id, x, y, w, h, q, surface; + var saved_pos = cmd.pos; var command = cmd.get_char(); lastSerial = cmd.get_32(); switch (command) { - case 'D': + case BROADWAY_OP_DISCONNECTED: alert ("disconnected"); inputSocket = null; break; - case 's': // create new surface + case BROADWAY_OP_NEW_SURFACE: id = cmd.get_16(); x = cmd.get_16s(); y = cmd.get_16s(); w = cmd.get_16(); h = cmd.get_16(); var isTemp = cmd.get_bool(); - cmdCreateSurface(id, x, y, w, h, isTemp); + var div = cmdCreateSurface(id, x, y, w, h, isTemp); + display_commands.push([DISPLAY_OP_APPEND_ROOT, div]); + need_restack = true; break; - case 'S': // Show a surface + case BROADWAY_OP_SHOW_SURFACE: id = cmd.get_16(); - cmdShowSurface(id); - break; + surface = surfaces[id]; + if (!surface.visible) { + surface.visible = true; + display_commands.push([DISPLAY_OP_SHOW_SURFACE, surface.div, surface.x, surface.y]); + need_restack = true; + } + break; - case 'H': // Hide a surface + case BROADWAY_OP_HIDE_SURFACE: id = cmd.get_16(); - cmdHideSurface(id); + if (grab.surface == id) + doUngrab(); + surface = surfaces[id]; + if (surface.visible) { + display_commands.push([DISPLAY_OP_HIDE_SURFACE, surface.div]); + } break; - case 'p': // Set transient parent + case BROADWAY_OP_SET_TRANSIENT_FOR: id = cmd.get_16(); var parentId = cmd.get_16(); - cmdSetTransientFor(id, parentId); + surface = surfaces[id]; + if (surface.transientParent !== parentId) { + surface.transientParent = parentId; + if (parentId != 0 && surfaces[parentId]) { + moveToHelper(surface, stackingOrder.indexOf(surfaces[parentId])+1); + } + need_restack = true; + } break; - case 'd': // Delete surface + case BROADWAY_OP_DESTROY_SURFACE: id = cmd.get_16(); - cmdDeleteSurface(id); + + if (grab.surface == id) + doUngrab(); + + surface = surfaces[id]; + var i = stackingOrder.indexOf(surface); + if (i >= 0) + stackingOrder.splice(i, 1); + var div = surface.div; + + display_commands.push([DISPLAY_OP_DELETE_NODE, div]); + // We need to delay this until its really deleted because we can still get events to it + display_commands.push([DISPLAY_OP_DELETE_SURFACE, id]); break; - case 'F': // RoundTrip + case BROADWAY_OP_ROUNDTRIP: id = cmd.get_16(); var tag = cmd.get_32(); cmdRoundtrip(id, tag); break; - case 'm': // Move a surface + case BROADWAY_OP_MOVE_RESIZE: id = cmd.get_16(); var ops = cmd.get_flags(); var has_pos = ops & 1; - if (has_pos) { - x = cmd.get_16s(); - y = cmd.get_16s(); - } var has_size = ops & 2; - if (has_size) { - w = cmd.get_16(); - h = cmd.get_16(); + surface = surfaces[id]; + if (has_pos) { + surface.positioned = true; + surface.x = cmd.get_16s();; + surface.y = cmd.get_16s();; + display_commands.push([DISPLAY_OP_MOVE_NODE, surface.div, surface.x, surface.y]); + } + if (has_size) { + surface.width = cmd.get_16(); + surface.height = cmd.get_16();; + display_commands.push([DISPLAY_OP_RESIZE_NODE, surface.div, surface.width, surface.height]); + } - cmdMoveResizeSurface(id, has_pos, x, y, has_size, w, h); break; - case 'r': // Raise a surface + case BROADWAY_OP_RAISE_SURFACE: id = cmd.get_16(); cmdRaiseSurface(id); + need_restack = true; break; - case 'R': // Lower a surface + case BROADWAY_OP_LOWER_SURFACE: id = cmd.get_16(); cmdLowerSurface(id); + need_restack = true; break; - case 't': // Upload texture + case BROADWAY_OP_UPLOAD_TEXTURE: id = cmd.get_32(); var data = cmd.get_data(); - cmdUploadTexture(id, data); + var texture = new Texture (id, data); // Stores a ref in global textures array + new_textures.push(texture); break; - case 'T': // Release texture + case BROADWAY_OP_RELEASE_TEXTURE: id = cmd.get_32(); - cmdReleaseTexture(id); + textures[id].unref(); break; - case 'n': // Set nodes + case BROADWAY_OP_SET_NODES: id = cmd.get_16(); - var len = cmd.get_32(); - var node_data = new Uint32Array(len); - for (var i = 0; i < len; i++) - node_data[i] = cmd.get_32(); + if (id in modified_trees) { + // Can't modify the same dom tree in the same loop, bail out and do the first one + cmd.pos = saved_pos; + res = false; + } else { + modified_trees[id] = true; - cmdSurfaceSetNodes(id, node_data); + var node_data = cmd.get_nodes (); + surface = surfaces[id]; + var transform_nodes = new TransformNodes (node_data, surface.div, display_commands); + transform_nodes.execute(); + } break; - case 'g': // Grab + case BROADWAY_OP_GRAB_POINTER: id = cmd.get_16(); var ownerEvents = cmd.get_bool (); cmdGrabPointer(id, ownerEvents); break; - case 'u': // Ungrab + case BROADWAY_OP_UNGRAB_POINTER: cmdUngrabPointer(); break; - case 'k': // show keyboard + case BROADWAY_OP_SET_SHOW_KEYBOARD: showKeyboard = cmd.get_16() != 0; showKeyboardChanged = true; break; @@ -856,59 +1018,115 @@ function handleCommands(cmd) alert("Unknown op " + command); } } - return true; + + if (need_restack) + display_commands.push([DISPLAY_OP_RESTACK_SURFACES]); + + return res; } +function handleOutstandingDisplayCommands() +{ + if (outstandingDisplayCommands) { + window.requestAnimationFrame( + function () { + handleDisplayCommands(outstandingDisplayCommands); + outstandingDisplayCommands = null; + + if (outstandingCommands.length > 0) + setTimeout(handleOutstanding); + }); + } else { + if (outstandingCommands.length > 0) + handleOutstanding (); + } +} + +/* Mode of operation. + * We run all outstandingCommands, until either we run out of things + * to process, or we update the dom nodes of the same surface twice. + * Then we wait for all textures to load, and then we request am + * animation frame and apply the display changes. Then we loop back and + * handle outstanding commands again. + * + * The reason for stopping if we update the same tree twice is that + * the delta operations generally assume that the previous dom tree + * is in pristine condition. + */ function handleOutstanding() { + var display_commands = new Array(); + var new_textures = new Array(); + var modified_trees = {}; + + if (outstandingDisplayCommands != null) + return; + while (outstandingCommands.length > 0) { var cmd = outstandingCommands.shift(); - if (!handleCommands(cmd)) { + if (!handleCommands(cmd, display_commands, new_textures, modified_trees)) { outstandingCommands.unshift(cmd); - return; + break; } } + + if (display_commands.length > 0) + outstandingDisplayCommands = display_commands; + + if (new_textures.length > 0) { + var n_textures = new_textures.length; + for (var i = 0; i < new_textures.length; i++) { + var t = new_textures[i]; + t.image.onload = function() { + n_textures -= 1; + if (n_textures == 0) { + handleOutstandingDisplayCommands(); + } + }; + } + } else { + handleOutstandingDisplayCommands(); + } + } function BinCommands(message) { this.arraybuffer = message; - this.u8 = new Uint8Array(message); - this.length = this.u8.length; + this.dataview = new DataView(message); + this.length = this.arraybuffer.byteLength; this.pos = 0; } BinCommands.prototype.get_char = function() { - return String.fromCharCode(this.u8[this.pos++]); + return String.fromCharCode(this.dataview.getUint8(this.pos++)); }; BinCommands.prototype.get_bool = function() { - return this.u8[this.pos++] != 0; + return this.dataview.getUint8(this.pos++) != 0; }; BinCommands.prototype.get_flags = function() { - return this.u8[this.pos++]; + return this.dataview.getUint8(this.pos++); } BinCommands.prototype.get_16 = function() { - var v = - this.u8[this.pos] + - (this.u8[this.pos+1] << 8); + var v = this.dataview.getUint16(this.pos, true); this.pos = this.pos + 2; return v; }; BinCommands.prototype.get_16s = function() { - var v = this.get_16 (); - if (v > 32767) - return v - 65536; - else - return v; + var v = this.dataview.getInt16(this.pos, true); + this.pos = this.pos + 2; + return v; }; BinCommands.prototype.get_32 = function() { - var v = - this.u8[this.pos] + - (this.u8[this.pos+1] << 8) + - (this.u8[this.pos+2] << 16) + - (this.u8[this.pos+3] << 24); + var v = this.dataview.getUint32(this.pos, true); this.pos = this.pos + 4; return v; }; +BinCommands.prototype.get_nodes = function() { + var len = this.get_32(); + var node_data = new DataView(this.arraybuffer, this.pos, len * 4); + this.pos = this.pos + len * 4; + return node_data; +}; BinCommands.prototype.get_data = function() { var size = this.get_32(); var data = new Uint8Array (this.arraybuffer, this.pos, size); @@ -916,8 +1134,14 @@ BinCommands.prototype.get_data = function() { return data; }; +var active = false; function handleMessage(message) { + if (!active) { + start(); + active = true; + } + var cmd = new BinCommands(message); outstandingCommands.push(cmd); if (outstandingCommands.length == 1) { @@ -1016,7 +1240,7 @@ function onMouseMove (ev) { var id = getSurfaceId(ev); id = getEffectiveEventTarget (id); var pos = getPositionsFromEvent(ev, id); - sendInput ("m", [realSurfaceWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState]); + sendInput (BROADWAY_EVENT_POINTER_MOVE, [realSurfaceWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState]); } function onMouseOver (ev) { @@ -1028,7 +1252,7 @@ function onMouseOver (ev) { var pos = getPositionsFromEvent(ev, id); surfaceWithMouse = id; if (surfaceWithMouse != 0) { - sendInput ("e", [realSurfaceWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_NORMAL]); + sendInput (BROADWAY_EVENT_ENTER, [realSurfaceWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_NORMAL]); } } @@ -1040,7 +1264,7 @@ function onMouseOut (ev) { var pos = getPositionsFromEvent(ev, id); if (id != 0) { - sendInput ("l", [realSurfaceWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_NORMAL]); + sendInput (BROADWAY_EVENT_LEAVE, [realSurfaceWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_NORMAL]); } realSurfaceWithMouse = 0; surfaceWithMouse = 0; @@ -1052,10 +1276,10 @@ function doGrab(id, ownerEvents, implicit) { if (surfaceWithMouse != id) { if (surfaceWithMouse != 0) { pos = getPositionsFromAbsCoord(lastX, lastY, surfaceWithMouse); - sendInput ("l", [realSurfaceWithMouse, surfaceWithMouse, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_GRAB]); + sendInput (BROADWAY_EVENT_LEAVE, [realSurfaceWithMouse, surfaceWithMouse, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_GRAB]); } pos = getPositionsFromAbsCoord(lastX, lastY, id); - sendInput ("e", [realSurfaceWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_GRAB]); + sendInput (BROADWAY_EVENT_ENTER, [realSurfaceWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_GRAB]); surfaceWithMouse = id; } @@ -1069,11 +1293,11 @@ function doUngrab() { if (realSurfaceWithMouse != surfaceWithMouse) { if (surfaceWithMouse != 0) { pos = getPositionsFromAbsCoord(lastX, lastY, surfaceWithMouse); - sendInput ("l", [realSurfaceWithMouse, surfaceWithMouse, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_UNGRAB]); + sendInput (BROADWAY_EVENT_LEAVE, [realSurfaceWithMouse, surfaceWithMouse, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_UNGRAB]); } if (realSurfaceWithMouse != 0) { pos = getPositionsFromAbsCoord(lastX, lastY, realSurfaceWithMouse); - sendInput ("e", [realSurfaceWithMouse, realSurfaceWithMouse, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_UNGRAB]); + sendInput (BROADWAY_EVENT_ENTER, [realSurfaceWithMouse, realSurfaceWithMouse, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_UNGRAB]); } surfaceWithMouse = realSurfaceWithMouse; } @@ -1090,7 +1314,7 @@ function onMouseDown (ev) { var pos = getPositionsFromEvent(ev, id); if (grab.surface == null) doGrab (id, false, true); - sendInput ("b", [realSurfaceWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, button]); + sendInput (BROADWAY_EVENT_BUTTON_PRESS, [realSurfaceWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, button]); return false; } @@ -1102,7 +1326,7 @@ function onMouseUp (ev) { id = getEffectiveEventTarget (evId); var pos = getPositionsFromEvent(ev, id); - sendInput ("B", [realSurfaceWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, button]); + sendInput (BROADWAY_EVENT_BUTTON_RELEASE, [realSurfaceWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, button]); if (grab.surface != null && grab.implicit) doUngrab(); @@ -2583,7 +2807,7 @@ function handleKeyDown(e) { // browser behaviors or it has no corresponding keyPress // event, then send it immediately if (!ignoreKeyEvent(ev)) - sendInput("k", [keysym, lastState]); + sendInput(BROADWAY_EVENT_KEY_PRESS, [keysym, lastState]); suppress = true; } @@ -2628,7 +2852,7 @@ function handleKeyPress(e) { // Send the translated keysym if (keysym > 0) - sendInput ("k", [keysym, lastState]); + sendInput (BROADWAY_EVENT_KEY_PRESS, [keysym, lastState]); // Stop keypress events just in case return cancelEvent(ev); @@ -2647,7 +2871,7 @@ function handleKeyUp(e) { } if (keysym > 0) - sendInput ("K", [keysym, lastState]); + sendInput (BROADWAY_EVENT_KEY_RELEASE, [keysym, lastState]); return cancelEvent(ev); } @@ -2691,7 +2915,7 @@ function onMouseWheel(ev) var dir = 0; if (offset > 0) dir = 1; - sendInput ("s", [realSurfaceWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, dir]); + sendInput (BROADWAY_EVENT_SCROLL, [realSurfaceWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, dir]); return cancelEvent(ev); } @@ -2716,17 +2940,17 @@ function onTouchStart(ev) { if (realSurfaceWithMouse != origId || id != surfaceWithMouse) { if (id != 0) { - sendInput ("l", [realSurfaceWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_NORMAL]); + sendInput (BROADWAY_EVENT_LEAVE, [realSurfaceWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_NORMAL]); } surfaceWithMouse = id; realSurfaceWithMouse = origId; - sendInput ("e", [origId, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_NORMAL]); + sendInput (BROADWAY_EVENT_ENTER, [origId, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_NORMAL]); } } - sendInput ("t", [0, id, touch.identifier, isEmulated, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState]); + sendInput (BROADWAY_EVENT_TOUCH, [0, id, touch.identifier, isEmulated, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState]); } } @@ -2748,7 +2972,7 @@ function onTouchMove(ev) { isEmulated = 1; } - sendInput ("t", [1, id, touch.identifier, isEmulated, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState]); + sendInput (BROADWAY_EVENT_TOUCH, [1, id, touch.identifier, isEmulated, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState]); } } @@ -2771,7 +2995,7 @@ function onTouchEnd(ev) { firstTouchDownId = null; } - sendInput ("t", [2, id, touch.identifier, isEmulated, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState]); + sendInput (BROADWAY_EVENT_TOUCH, [2, id, touch.identifier, isEmulated, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState]); } } @@ -2809,9 +3033,9 @@ function start() var w, h; w = window.innerWidth; h = window.innerHeight; - sendInput ("d", [w, h]); + sendInput (BROADWAY_EVENT_SCREEN_SIZE_CHANGED, [w, h]); }; - sendInput ("d", [w, h]); + sendInput (BROADWAY_EVENT_SCREEN_SIZE_CHANGED, [w, h]); } function connect() diff --git a/gdk/broadway/broadwayd.c b/gdk/broadway/broadwayd.c index e453af463c..a3b402f1f6 100644 --- a/gdk/broadway/broadwayd.c +++ b/gdk/broadway/broadwayd.c @@ -215,116 +215,6 @@ get_client_serial (BroadwayClient *client, guint32 daemon_serial) return client_serial; } -#define NODE_SIZE_COLOR 1 -#define NODE_SIZE_FLOAT 1 -#define NODE_SIZE_POINT 2 -#define NODE_SIZE_SIZE 2 -#define NODE_SIZE_RECT (NODE_SIZE_POINT + NODE_SIZE_SIZE) -#define NODE_SIZE_RRECT (NODE_SIZE_RECT + 4 * NODE_SIZE_SIZE) -#define NODE_SIZE_COLOR_STOP (NODE_SIZE_FLOAT + NODE_SIZE_COLOR) -#define NODE_SIZE_SHADOW (NODE_SIZE_COLOR + 3 * NODE_SIZE_FLOAT) - -static guint32 -rotl (guint32 value, int shift) -{ - if ((shift &= 32 - 1) == 0) - return value; - return (value << shift) | (value >> (32 - shift)); -} - -static BroadwayNode * -decode_nodes (BroadwayClient *client, - int len, guint32 data[], int *pos) -{ - BroadwayNode *node; - guint32 type; - guint32 i, n_stops, n_shadows; - guint32 size, n_children; - gint32 texture_offset; - guint32 hash; - - g_assert (*pos < len); - - size = 0; - n_children = 0; - texture_offset = -1; - - type = data[(*pos)++]; - switch (type) { - case BROADWAY_NODE_COLOR: - size = NODE_SIZE_RECT + NODE_SIZE_COLOR; - break; - case BROADWAY_NODE_BORDER: - size = NODE_SIZE_RRECT + 4 * NODE_SIZE_FLOAT + 4 * NODE_SIZE_COLOR; - break; - case BROADWAY_NODE_INSET_SHADOW: - case BROADWAY_NODE_OUTSET_SHADOW: - size = NODE_SIZE_RRECT + NODE_SIZE_COLOR + 4 * NODE_SIZE_FLOAT; - break; - case BROADWAY_NODE_TEXTURE: - texture_offset = 4; - size = 5; - break; - case BROADWAY_NODE_CONTAINER: - size = 1; - n_children = data[*pos]; - break; - case BROADWAY_NODE_ROUNDED_CLIP: - size = NODE_SIZE_RRECT; - n_children = 1; - break; - case BROADWAY_NODE_CLIP: - size = NODE_SIZE_RECT; - n_children = 1; - break; - case BROADWAY_NODE_LINEAR_GRADIENT: - size = NODE_SIZE_RECT + 2 * NODE_SIZE_POINT; - n_stops = data[*pos + size++]; - size += n_stops * NODE_SIZE_COLOR_STOP; - break; - case BROADWAY_NODE_SHADOW: - size = 1; - n_shadows = data[*pos]; - size += n_shadows * NODE_SIZE_SHADOW; - n_children = 1; - break; - case BROADWAY_NODE_OPACITY: - size = NODE_SIZE_FLOAT; - n_children = 1; - break; - default: - g_assert_not_reached (); - } - - node = g_malloc (sizeof(BroadwayNode) + (size - 1) * sizeof(guint32) + n_children * sizeof (BroadwayNode *)); - node->type = type; - node->n_children = n_children; - node->children = (BroadwayNode **)((char *)node + sizeof(BroadwayNode) + (size - 1) * sizeof(guint32)); - node->n_data = size; - for (i = 0; i < size; i++) - { - node->data[i] = data[(*pos)++]; - if (i == texture_offset) - node->data[i] = GPOINTER_TO_INT (g_hash_table_lookup (client->textures, - GINT_TO_POINTER (node->data[i]))); - } - - for (i = 0; i < n_children; i++) - node->children[i] = decode_nodes (client, len, data, pos); - - hash = node->type << 16; - - for (i = 0; i < size; i++) - hash ^= rotl (node->data[i], i); - - for (i = 0; i < n_children; i++) - hash ^= rotl (node->children[i]->hash, i); - - node->hash = hash; - - return node; -} - static void client_handle_request (BroadwayClient *client, BroadwayRequest *request) @@ -400,13 +290,10 @@ client_handle_request (BroadwayClient *client, { gsize array_size = request->base.size - sizeof (BroadwayRequestSetNodes) + sizeof(guint32); int n_data = array_size / sizeof(guint32); - int pos = 0; - BroadwayNode *node; - node = decode_nodes (client, n_data, request->set_nodes.data, &pos); - - broadway_server_surface_set_nodes (server, request->set_nodes.id, - node); + broadway_server_surface_update_nodes (server, request->set_nodes.id, + request->set_nodes.data, n_data, + client->textures); } break; case BROADWAY_REQUEST_UPLOAD_TEXTURE: diff --git a/gdk/broadway/gdkdisplay-broadway.c b/gdk/broadway/gdkdisplay-broadway.c index f27af0b3ef..9a80ec8d55 100644 --- a/gdk/broadway/gdkdisplay-broadway.c +++ b/gdk/broadway/gdkdisplay-broadway.c @@ -380,7 +380,7 @@ gdk_broadway_display_ensure_texture (GdkDisplay *display, data = g_new0 (BroadwayTextureData, 1); data->id = id; data->display = g_object_ref (display); - g_object_set_data_full (G_OBJECT (texture), "broadway-data", data, (GDestroyNotify)broadway_texture_data_free); + g_object_set_data_full (G_OBJECT (texture), "broadway-data", data, (GDestroyNotify)broadway_texture_data_free); } return data->id; diff --git a/gdk/broadway/gdkdrawcontext-broadway.c b/gdk/broadway/gdkdrawcontext-broadway.c index 889b52fb30..473488396a 100644 --- a/gdk/broadway/gdkdrawcontext-broadway.c +++ b/gdk/broadway/gdkdrawcontext-broadway.c @@ -64,6 +64,8 @@ gdk_broadway_draw_context_end_frame (GdkDrawContext *draw_context, g_array_unref (self->nodes); self->nodes = NULL; + + /* We now sent all new texture refs to the daemon via the nodes, so we can drop them here */ g_ptr_array_unref (self->node_textures); self->node_textures = NULL; } diff --git a/gdk/broadway/gdksurface-broadway.c b/gdk/broadway/gdksurface-broadway.c index 56e9684112..4a3b02d8b1 100644 --- a/gdk/broadway/gdksurface-broadway.c +++ b/gdk/broadway/gdksurface-broadway.c @@ -1232,6 +1232,9 @@ gdk_broadway_surface_begin_resize_drag (GdkSurface *surface, mv_resize = get_move_resize_data (gdk_surface_get_display (surface), TRUE); + if (mv_resize->moveresize_surface != NULL) + return; /* already a drag operation in progress */ + mv_resize->is_resize = TRUE; mv_resize->moveresize_button = button; mv_resize->resize_edge = edge; @@ -1272,6 +1275,9 @@ gdk_broadway_surface_begin_move_drag (GdkSurface *surface, mv_resize = get_move_resize_data (gdk_surface_get_display (surface), TRUE); + if (mv_resize->moveresize_surface != NULL) + return; /* already a drag operation in progress */ + mv_resize->is_resize = FALSE; mv_resize->moveresize_button = button; mv_resize->moveresize_x = root_x; diff --git a/gsk/gskbroadwayrenderer.c b/gsk/gskbroadwayrenderer.c index 84b948d676..0730ca8a73 100644 --- a/gsk/gskbroadwayrenderer.c +++ b/gsk/gskbroadwayrenderer.c @@ -4,6 +4,7 @@ #include "broadway/gdkprivate-broadway.h" #include "gskdebugprivate.h" +#include "gsktransformprivate.h" #include "gskrendererprivate.h" #include "gskrendernodeprivate.h" #include "gdk/gdktextureprivate.h" @@ -12,6 +13,16 @@ struct _GskBroadwayRenderer { GskRenderer parent_instance; GdkBroadwayDrawContext *draw_context; + guint32 next_node_id; + + /* Set during rendering */ + GArray *nodes; /* Owned by draw_contex */ + GPtrArray *node_textures; /* Owned by draw_contex */ + GHashTable *node_lookup; + + /* Kept from last frame */ + GHashTable *last_node_lookup; + GskRenderNode *last_root; /* Owning refs to the things in last_node_lookup */ }; struct _GskBroadwayRendererClass @@ -71,12 +82,25 @@ gsk_broadway_renderer_render_texture (GskRenderer *renderer, return texture; } +/* uint32 is sent in native endianness, and then converted to little endian in broadwayd when sending to browser */ static void add_uint32 (GArray *nodes, guint32 v) { g_array_append_val (nodes, v); } +static void +add_float (GArray *nodes, float f) +{ + union { + float f; + guint32 i; + } u; + + u.f = f; + g_array_append_val (nodes, u.i); +} + static guint32 rgba_to_uint32 (const GdkRGBA *rgba) { @@ -96,18 +120,16 @@ add_rgba (GArray *nodes, const GdkRGBA *rgba) } static void -add_float (GArray *nodes, float f) +add_xy (GArray *nodes, float x, float y, float offset_x, float offset_y) { - gint32 i = (gint32) (f * 256.0f); - guint u = (guint32) i; - g_array_append_val (nodes, u); + add_float (nodes, x - offset_x); + add_float (nodes, y - offset_y); } static void add_point (GArray *nodes, const graphene_point_t *point, float offset_x, float offset_y) { - add_float (nodes, point->x - offset_x); - add_float (nodes, point->y - offset_y); + add_xy (nodes, point->x, point->y, offset_x, offset_y); } static void @@ -140,313 +162,157 @@ add_color_stop (GArray *nodes, const GskColorStop *stop) add_rgba (nodes, &stop->color); } -static gboolean -float_is_int32 (float f) -{ - gint32 i = (gint32)f; - float f2 = (float)i; - return f2 == f; -} - -static GHashTable *gsk_broadway_node_cache; - -typedef struct { - GdkTexture *texture; - GskRenderNode *node; - float off_x; - float off_y; -} NodeCacheElement; - static void -node_cache_element_free (NodeCacheElement *element) +add_string (GArray *nodes, const char *str) { - gsk_render_node_unref (element->node); - g_free (element); -} + guint32 len = strlen(str); + guint32 v, c; -static guint -glyph_info_hash (const PangoGlyphInfo *info) -{ - return info->glyph ^ - info->geometry.width << 6 ^ - info->geometry.x_offset << 12 ^ - info->geometry.y_offset << 18 ^ - info->attr.is_cluster_start << 30; -} + add_uint32 (nodes, len); -static gboolean -glyph_info_equal (const PangoGlyphInfo *a, - const PangoGlyphInfo *b) -{ - return - a->glyph == b->glyph && - a->geometry.width == b->geometry.width && - a->geometry.x_offset == b->geometry.x_offset && - a->geometry.y_offset == b->geometry.y_offset && - a->attr.is_cluster_start == b->attr.is_cluster_start; - } - -static guint -hash_matrix (const graphene_matrix_t *matrix) -{ - float m[16]; - guint h = 0; - int i; - - graphene_matrix_to_float (matrix, m); - for (i = 0; i < 16; i++) - h ^= (guint) m[i]; - - return h; -} - -static gboolean -matrix_equal (const graphene_matrix_t *a, - const graphene_matrix_t *b) -{ - float ma[16]; - float mb[16]; - int i; - - graphene_matrix_to_float (a, ma); - graphene_matrix_to_float (b, mb); - for (i = 0; i < 16; i++) + v = 0; + c = 0; + while (*str != 0) { - if (ma[i] != mb[i]) - return FALSE; - } - - return TRUE; -} - -static guint -hash_vec4 (const graphene_vec4_t *vec4) -{ - float v[4]; - guint h = 0; - int i; - - graphene_vec4_to_float (vec4, v); - for (i = 0; i < 4; i++) - h ^= (guint) v[i]; - - return h; -} - -static gboolean -vec4_equal (const graphene_vec4_t *a, - const graphene_vec4_t *b) -{ - float va[4]; - float vb[4]; - int i; - - graphene_vec4_to_float (a, va); - graphene_vec4_to_float (b, vb); - for (i = 0; i < 4; i++) - { - if (va[i] != vb[i]) - return FALSE; - } - - return TRUE; -} - -static guint -node_cache_hash (GskRenderNode *node) -{ - GskRenderNodeType type; - guint h; - - type = gsk_render_node_get_node_type (node); - h = type << 28; - if (type == GSK_TEXT_NODE && - float_is_int32 (gsk_text_node_get_x (node)) && - float_is_int32 (gsk_text_node_get_y (node))) - { - guint i; - const PangoFont *font = gsk_text_node_peek_font (node); - guint n_glyphs = gsk_text_node_get_num_glyphs (node); - const PangoGlyphInfo *infos = gsk_text_node_peek_glyphs (node); - const GdkRGBA *color = gsk_text_node_peek_color (node); - - h ^= g_direct_hash (font) ^ n_glyphs << 16 ^ gdk_rgba_hash (color); - for (i = 0; i < n_glyphs; i++) - h ^= glyph_info_hash (&infos[i]); - - return h; - } - - if (type == GSK_COLOR_MATRIX_NODE && - gsk_render_node_get_node_type (gsk_color_matrix_node_get_child (node)) == GSK_TEXTURE_NODE) - { - const graphene_matrix_t *matrix = gsk_color_matrix_node_peek_color_matrix (node); - const graphene_vec4_t *offset = gsk_color_matrix_node_peek_color_offset (node); - GskRenderNode *child = gsk_color_matrix_node_get_child (node); - GdkTexture *texture = gsk_texture_node_get_texture (child); - - h ^= g_direct_hash (texture) ^ hash_matrix (matrix) ^ hash_vec4 (offset); - - return h; - } - - return 0; -} - -static gboolean -node_cache_equal (GskRenderNode *a, - GskRenderNode *b) -{ - GskRenderNodeType type; - - type = gsk_render_node_get_node_type (a); - if (type != gsk_render_node_get_node_type (b)) - return FALSE; - - if (type == GSK_TEXT_NODE && - float_is_int32 (gsk_text_node_get_x (a)) && - float_is_int32 (gsk_text_node_get_y (a)) && - float_is_int32 (gsk_text_node_get_x (b)) && - float_is_int32 (gsk_text_node_get_y (b))) - { - const PangoFont *a_font = gsk_text_node_peek_font (a); - guint a_n_glyphs = gsk_text_node_get_num_glyphs (a); - const PangoGlyphInfo *a_infos = gsk_text_node_peek_glyphs (a); - const GdkRGBA *a_color = gsk_text_node_peek_color (a); - const PangoFont *b_font = gsk_text_node_peek_font (b); - guint b_n_glyphs = gsk_text_node_get_num_glyphs (b); - const PangoGlyphInfo *b_infos = gsk_text_node_peek_glyphs (b); - const GdkRGBA *b_color = gsk_text_node_peek_color (a); - guint i; - - if (a_font != b_font) - return FALSE; - - if (a_n_glyphs != b_n_glyphs) - return FALSE; - - for (i = 0; i < a_n_glyphs; i++) + v |= (*str++) << 8*c++; + if (c == 4) { - if (!glyph_info_equal (&a_infos[i], &b_infos[i])) - return FALSE; + add_uint32 (nodes, v); + v = 0; + c = 0; } - - if (!gdk_rgba_equal (a_color, b_color)) - return FALSE; - - return TRUE; } - if (type == GSK_COLOR_MATRIX_NODE && - gsk_render_node_get_node_type (gsk_color_matrix_node_get_child (a)) == GSK_TEXTURE_NODE && - gsk_render_node_get_node_type (gsk_color_matrix_node_get_child (b)) == GSK_TEXTURE_NODE) - { - const graphene_matrix_t *a_matrix = gsk_color_matrix_node_peek_color_matrix (a); - const graphene_vec4_t *a_offset = gsk_color_matrix_node_peek_color_offset (a); - GskRenderNode *a_child = gsk_color_matrix_node_get_child (a); - GdkTexture *a_texture = gsk_texture_node_get_texture (a_child); - const graphene_matrix_t *b_matrix = gsk_color_matrix_node_peek_color_matrix (b); - const graphene_vec4_t *b_offset = gsk_color_matrix_node_peek_color_offset (b); - GskRenderNode *b_child = gsk_color_matrix_node_get_child (b); - GdkTexture *b_texture = gsk_texture_node_get_texture (b_child); - - if (a_texture != b_texture) - return FALSE; - - if (!matrix_equal (a_matrix, b_matrix)) - return FALSE; - - if (!vec4_equal (a_offset, b_offset)) - return FALSE; - - return TRUE; - } - - return FALSE; -} - -static GdkTexture * -node_cache_lookup (GskRenderNode *node, - float *off_x, float *off_y) -{ - NodeCacheElement *hit; - - if (gsk_broadway_node_cache == NULL) - gsk_broadway_node_cache = g_hash_table_new_full ((GHashFunc)node_cache_hash, - (GEqualFunc)node_cache_equal, - NULL, - (GDestroyNotify)node_cache_element_free); - - hit = g_hash_table_lookup (gsk_broadway_node_cache, node); - if (hit) - { - *off_x = hit->off_x; - *off_y = hit->off_y; - return g_object_ref (hit->texture); - } - - return NULL; + if (c != 0) + add_uint32 (nodes, v); } static void -cached_texture_gone (gpointer data, - GObject *where_the_object_was) -{ - NodeCacheElement *element = data; - g_hash_table_remove (gsk_broadway_node_cache, element->node); -} +collect_reused_child_nodes (GskRenderer *renderer, + GskRenderNode *node); static void -node_cache_store (GskRenderNode *node, - GdkTexture *texture, - float off_x, - float off_y) +collect_reused_node (GskRenderer *renderer, + GskRenderNode *node) { - GskRenderNodeType type; + GskBroadwayRenderer *self = GSK_BROADWAY_RENDERER (renderer); + guint32 old_id; - type = gsk_render_node_get_node_type (node); - if ((type == GSK_TEXT_NODE && - float_is_int32 (gsk_text_node_get_x (node)) && - float_is_int32 (gsk_text_node_get_y (node))) || - (type == GSK_COLOR_MATRIX_NODE && - gsk_render_node_get_node_type (gsk_color_matrix_node_get_child (node)) == GSK_TEXTURE_NODE)) + if (self->last_node_lookup && + (old_id = GPOINTER_TO_INT(g_hash_table_lookup (self->last_node_lookup, node))) != 0) { - NodeCacheElement *element = g_new0 (NodeCacheElement, 1); - element->texture = texture; - element->node = gsk_render_node_ref (node); - element->off_x = off_x; - element->off_y = off_y; - g_object_weak_ref (G_OBJECT (texture), cached_texture_gone, element); - g_hash_table_insert (gsk_broadway_node_cache, element->node, element); + g_hash_table_insert (self->node_lookup, node, GINT_TO_POINTER (old_id)); + collect_reused_child_nodes (renderer, node); + } +} + +static void +collect_reused_child_nodes (GskRenderer *renderer, + GskRenderNode *node) +{ + guint i; + + switch (gsk_render_node_get_node_type (node)) + { + case GSK_NOT_A_RENDER_NODE: + g_assert_not_reached (); return; + + /* Leaf nodes */ + + case GSK_TEXTURE_NODE: + case GSK_CAIRO_NODE: + case GSK_COLOR_NODE: + case GSK_BORDER_NODE: + case GSK_OUTSET_SHADOW_NODE: + case GSK_INSET_SHADOW_NODE: + case GSK_LINEAR_GRADIENT_NODE: + + /* Fallbacks (=> leaf for now */ + case GSK_COLOR_MATRIX_NODE: + case GSK_TEXT_NODE: + case GSK_REPEATING_LINEAR_GRADIENT_NODE: + case GSK_REPEAT_NODE: + case GSK_BLEND_NODE: + case GSK_CROSS_FADE_NODE: + case GSK_BLUR_NODE: + + default: + + break; + + /* Bin nodes */ + + case GSK_SHADOW_NODE: + collect_reused_node (renderer, + gsk_shadow_node_get_child (node)); + break; + + case GSK_OPACITY_NODE: + collect_reused_node (renderer, + gsk_opacity_node_get_child (node)); + break; + + case GSK_ROUNDED_CLIP_NODE: + collect_reused_node (renderer, + gsk_rounded_clip_node_get_child (node)); + break; + + case GSK_CLIP_NODE: + collect_reused_node (renderer, + gsk_clip_node_get_child (node)); + break; + + case GSK_TRANSFORM_NODE: + collect_reused_node (renderer, + gsk_transform_node_get_child (node)); + break; + + case GSK_DEBUG_NODE: + collect_reused_node (renderer, + gsk_debug_node_get_child (node)); + break; + + /* Generic nodes */ + + case GSK_CONTAINER_NODE: + for (i = 0; i < gsk_container_node_get_n_children (node); i++) + collect_reused_node (renderer, + gsk_container_node_get_child (node, i)); + break; + + break; /* Fallback */ } } -static GdkTexture * -node_texture_fallback (GskRenderNode *node, - float *off_x, - float *off_y) +static gboolean +add_new_node (GskRenderer *renderer, + GskRenderNode *node, + BroadwayNodeType type) { - cairo_surface_t *surface; - cairo_t *cr; - int x = floorf (node->bounds.origin.x); - int y = floorf (node->bounds.origin.y); - int width = ceil (node->bounds.origin.x + node->bounds.size.width) - x; - int height = ceil (node->bounds.origin.y + node->bounds.size.height) - y; - GdkTexture *texture; + GskBroadwayRenderer *self = GSK_BROADWAY_RENDERER (renderer); + guint32 id, old_id; - surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height); - cr = cairo_create (surface); - cairo_translate (cr, -x, -y); - gsk_render_node_draw (node, cr); - cairo_destroy (cr); + if (self->last_node_lookup && + (old_id = GPOINTER_TO_INT (g_hash_table_lookup (self->last_node_lookup, node))) != 0) + { + add_uint32 (self->nodes, BROADWAY_NODE_REUSE); + add_uint32 (self->nodes, old_id); - texture = gdk_texture_new_for_surface (surface); - *off_x = x - node->bounds.origin.x; - *off_y = y - node->bounds.origin.y; + g_hash_table_insert (self->node_lookup, node, GINT_TO_POINTER(old_id)); + collect_reused_child_nodes (renderer, node); - return texture; + return FALSE; + } + + id = ++self->next_node_id; + g_hash_table_insert (self->node_lookup, node, GINT_TO_POINTER(id)); + + add_uint32 (self->nodes, type); + add_uint32 (self->nodes, id); + + return TRUE; } /* Note: This tracks the offset so that we can convert @@ -455,13 +321,13 @@ node_texture_fallback (GskRenderNode *node, which is good for re-using subtrees. */ static void gsk_broadway_renderer_add_node (GskRenderer *renderer, - GArray *nodes, - GPtrArray *node_textures, GskRenderNode *node, float offset_x, float offset_y) { GdkDisplay *display = gdk_surface_get_display (gsk_renderer_get_surface (renderer)); + GskBroadwayRenderer *self = GSK_BROADWAY_RENDERER (renderer); + GArray *nodes = self->nodes; switch (gsk_render_node_get_node_type (node)) { @@ -472,191 +338,223 @@ gsk_broadway_renderer_add_node (GskRenderer *renderer, /* Leaf nodes */ case GSK_TEXTURE_NODE: - { - GdkTexture *texture = gsk_texture_node_get_texture (node); - guint32 texture_id; + if (add_new_node (renderer, node, BROADWAY_NODE_TEXTURE)) + { + GdkTexture *texture = gsk_texture_node_get_texture (node); + guint32 texture_id; - g_ptr_array_add (node_textures, g_object_ref (texture)); /* Transfers ownership to node_textures */ - texture_id = gdk_broadway_display_ensure_texture (display, texture); + /* No need to add to self->node_textures here, the node will keep it alive until end of frame. */ - add_uint32 (nodes, BROADWAY_NODE_TEXTURE); - add_rect (nodes, &node->bounds, offset_x, offset_y); - add_uint32 (nodes, texture_id); - } + texture_id = gdk_broadway_display_ensure_texture (display, texture); + + add_rect (nodes, &node->bounds, offset_x, offset_y); + add_uint32 (nodes, texture_id); + } return; case GSK_CAIRO_NODE: - { - cairo_surface_t *surface = (cairo_surface_t *)gsk_cairo_node_peek_surface (node); - cairo_surface_t *image_surface = NULL; - GdkTexture *texture; - guint32 texture_id; + if (add_new_node (renderer, node, BROADWAY_NODE_TEXTURE)) + { + cairo_surface_t *surface = (cairo_surface_t *)gsk_cairo_node_peek_surface (node); + cairo_surface_t *image_surface = NULL; + GdkTexture *texture; + guint32 texture_id; - if (cairo_surface_get_type (surface) == CAIRO_SURFACE_TYPE_IMAGE) - image_surface = cairo_surface_reference (surface); - else - { - cairo_t *cr; - image_surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, - ceilf (node->bounds.size.width), - ceilf (node->bounds.size.height)); - cr = cairo_create (image_surface); - cairo_set_source_surface (cr, surface, 0, 0); - cairo_rectangle (cr, 0, 0, node->bounds.size.width, node->bounds.size.height); - cairo_fill (cr); - cairo_destroy (cr); - } + if (cairo_surface_get_type (surface) == CAIRO_SURFACE_TYPE_IMAGE) + image_surface = cairo_surface_reference (surface); + else + { + cairo_t *cr; + image_surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, + ceilf (node->bounds.size.width), + ceilf (node->bounds.size.height)); + cr = cairo_create (image_surface); + cairo_set_source_surface (cr, surface, 0, 0); + cairo_rectangle (cr, 0, 0, node->bounds.size.width, node->bounds.size.height); + cairo_fill (cr); + cairo_destroy (cr); + } - texture = gdk_texture_new_for_surface (image_surface); - g_ptr_array_add (node_textures, g_object_ref (texture)); /* Transfers ownership to node_textures */ - texture_id = gdk_broadway_display_ensure_texture (display, texture); + texture = gdk_texture_new_for_surface (image_surface); + g_ptr_array_add (self->node_textures, g_object_ref (texture)); /* Transfers ownership to node_textures */ + texture_id = gdk_broadway_display_ensure_texture (display, texture); - add_uint32 (nodes, BROADWAY_NODE_TEXTURE); - add_rect (nodes, &node->bounds, offset_x, offset_y); - add_uint32 (nodes, texture_id); + add_rect (nodes, &node->bounds, offset_x, offset_y); + add_uint32 (nodes, texture_id); - cairo_surface_destroy (image_surface); - } + cairo_surface_destroy (image_surface); + } return; case GSK_COLOR_NODE: - { - add_uint32 (nodes, BROADWAY_NODE_COLOR); - add_rect (nodes, &node->bounds, offset_x, offset_y); - add_rgba (nodes, gsk_color_node_peek_color (node)); - } + if (add_new_node (renderer, node, BROADWAY_NODE_COLOR)) + { + add_rect (nodes, &node->bounds, offset_x, offset_y); + add_rgba (nodes, gsk_color_node_peek_color (node)); + } return; case GSK_BORDER_NODE: - { - int i; - add_uint32 (nodes, BROADWAY_NODE_BORDER); - add_rounded_rect (nodes, gsk_border_node_peek_outline (node), offset_x, offset_y); - for (i = 0; i < 4; i++) - add_float (nodes, gsk_border_node_peek_widths (node)[i]); - for (i = 0; i < 4; i++) - add_rgba (nodes, &gsk_border_node_peek_colors (node)[i]); - } + if (add_new_node (renderer, node, BROADWAY_NODE_BORDER)) + { + int i; + add_rounded_rect (nodes, gsk_border_node_peek_outline (node), offset_x, offset_y); + for (i = 0; i < 4; i++) + add_float (nodes, gsk_border_node_peek_widths (node)[i]); + for (i = 0; i < 4; i++) + add_rgba (nodes, &gsk_border_node_peek_colors (node)[i]); + } return; case GSK_OUTSET_SHADOW_NODE: - { - add_uint32 (nodes, BROADWAY_NODE_OUTSET_SHADOW); - add_rounded_rect (nodes, gsk_outset_shadow_node_peek_outline (node), offset_x, offset_y); - add_rgba (nodes, gsk_outset_shadow_node_peek_color (node)); - add_float (nodes, gsk_outset_shadow_node_get_dx (node)); - add_float (nodes, gsk_outset_shadow_node_get_dy (node)); - add_float (nodes, gsk_outset_shadow_node_get_spread (node)); - add_float (nodes, gsk_outset_shadow_node_get_blur_radius (node)); - } + if (add_new_node (renderer, node, BROADWAY_NODE_OUTSET_SHADOW)) + { + add_rounded_rect (nodes, gsk_outset_shadow_node_peek_outline (node), offset_x, offset_y); + add_rgba (nodes, gsk_outset_shadow_node_peek_color (node)); + add_float (nodes, gsk_outset_shadow_node_get_dx (node)); + add_float (nodes, gsk_outset_shadow_node_get_dy (node)); + add_float (nodes, gsk_outset_shadow_node_get_spread (node)); + add_float (nodes, gsk_outset_shadow_node_get_blur_radius (node)); + } return; case GSK_INSET_SHADOW_NODE: - { - add_uint32 (nodes, BROADWAY_NODE_INSET_SHADOW); - add_rounded_rect (nodes, gsk_inset_shadow_node_peek_outline (node), offset_x, offset_y); - add_rgba (nodes, gsk_inset_shadow_node_peek_color (node)); - add_float (nodes, gsk_inset_shadow_node_get_dx (node)); - add_float (nodes, gsk_inset_shadow_node_get_dy (node)); - add_float (nodes, gsk_inset_shadow_node_get_spread (node)); - add_float (nodes, gsk_inset_shadow_node_get_blur_radius (node)); - } + if (add_new_node (renderer, node, BROADWAY_NODE_INSET_SHADOW)) + { + add_rounded_rect (nodes, gsk_inset_shadow_node_peek_outline (node), offset_x, offset_y); + add_rgba (nodes, gsk_inset_shadow_node_peek_color (node)); + add_float (nodes, gsk_inset_shadow_node_get_dx (node)); + add_float (nodes, gsk_inset_shadow_node_get_dy (node)); + add_float (nodes, gsk_inset_shadow_node_get_spread (node)); + add_float (nodes, gsk_inset_shadow_node_get_blur_radius (node)); + } return; case GSK_LINEAR_GRADIENT_NODE: - { - guint i, n; + if (add_new_node (renderer, node, BROADWAY_NODE_LINEAR_GRADIENT)) + { + guint i, n; - add_uint32 (nodes, BROADWAY_NODE_LINEAR_GRADIENT); - add_rect (nodes, &node->bounds, offset_x, offset_y); - add_point (nodes, gsk_linear_gradient_node_peek_start (node), offset_x, offset_y); - add_point (nodes, gsk_linear_gradient_node_peek_end (node), offset_x, offset_y); - n = gsk_linear_gradient_node_get_n_color_stops (node); - add_uint32 (nodes, n); - for (i = 0; i < n; i++) - add_color_stop (nodes, &gsk_linear_gradient_node_peek_color_stops (node)[i]); - } + add_rect (nodes, &node->bounds, offset_x, offset_y); + add_point (nodes, gsk_linear_gradient_node_peek_start (node), offset_x, offset_y); + add_point (nodes, gsk_linear_gradient_node_peek_end (node), offset_x, offset_y); + n = gsk_linear_gradient_node_get_n_color_stops (node); + add_uint32 (nodes, n); + for (i = 0; i < n; i++) + add_color_stop (nodes, &gsk_linear_gradient_node_peek_color_stops (node)[i]); + } return; /* Bin nodes */ case GSK_SHADOW_NODE: - { - gsize i, n_shadows = gsk_shadow_node_get_n_shadows (node); - add_uint32 (nodes, BROADWAY_NODE_SHADOW); - add_uint32 (nodes, n_shadows); - for (i = 0; i < n_shadows; i++) - { - const GskShadow *shadow = gsk_shadow_node_peek_shadow (node, i); - add_rgba (nodes, &shadow->color); - add_float (nodes, shadow->dx); - add_float (nodes, shadow->dy); - add_float (nodes, shadow->radius); - } - gsk_broadway_renderer_add_node (renderer, nodes, node_textures, - gsk_shadow_node_get_child (node), - offset_x, offset_y); - } + if (add_new_node (renderer, node, BROADWAY_NODE_SHADOW)) + { + gsize i, n_shadows = gsk_shadow_node_get_n_shadows (node); + + add_uint32 (nodes, n_shadows); + for (i = 0; i < n_shadows; i++) + { + const GskShadow *shadow = gsk_shadow_node_peek_shadow (node, i); + add_rgba (nodes, &shadow->color); + add_float (nodes, shadow->dx); + add_float (nodes, shadow->dy); + add_float (nodes, shadow->radius); + } + gsk_broadway_renderer_add_node (renderer, + gsk_shadow_node_get_child (node), + offset_x, offset_y); + } return; case GSK_OPACITY_NODE: - { - add_uint32 (nodes, BROADWAY_NODE_OPACITY); - add_float (nodes, gsk_opacity_node_get_opacity (node)); - gsk_broadway_renderer_add_node (renderer, nodes, node_textures, - gsk_opacity_node_get_child (node), - offset_x, offset_y); - } + if (add_new_node (renderer, node, BROADWAY_NODE_OPACITY)) + { + add_float (nodes, gsk_opacity_node_get_opacity (node)); + gsk_broadway_renderer_add_node (renderer, + gsk_opacity_node_get_child (node), + offset_x, offset_y); + } return; case GSK_ROUNDED_CLIP_NODE: - { - const GskRoundedRect *rclip = gsk_rounded_clip_node_peek_clip (node); - add_uint32 (nodes, BROADWAY_NODE_ROUNDED_CLIP); - add_rounded_rect (nodes, rclip, offset_x, offset_y); - gsk_broadway_renderer_add_node (renderer, nodes, node_textures, - gsk_rounded_clip_node_get_child (node), - rclip->bounds.origin.x, - rclip->bounds.origin.y); - } + if (add_new_node (renderer, node, BROADWAY_NODE_ROUNDED_CLIP)) + { + const GskRoundedRect *rclip = gsk_rounded_clip_node_peek_clip (node); + + add_rounded_rect (nodes, rclip, offset_x, offset_y); + gsk_broadway_renderer_add_node (renderer, + gsk_rounded_clip_node_get_child (node), + rclip->bounds.origin.x, + rclip->bounds.origin.y); + } return; case GSK_CLIP_NODE: + if (add_new_node (renderer, node, BROADWAY_NODE_CLIP)) + { + const graphene_rect_t *clip = gsk_clip_node_peek_clip (node); + + add_rect (nodes, clip, offset_x, offset_y); + gsk_broadway_renderer_add_node (renderer, + gsk_clip_node_get_child (node), + clip->origin.x, + clip->origin.y); + } + return; + + case GSK_TRANSFORM_NODE: { - const graphene_rect_t *clip = gsk_clip_node_peek_clip (node); - add_uint32 (nodes, BROADWAY_NODE_CLIP); - add_rect (nodes, clip, offset_x, offset_y); - gsk_broadway_renderer_add_node (renderer, nodes, node_textures, - gsk_clip_node_get_child (node), - clip->origin.x, - clip->origin.y); + GskTransform *transform = gsk_transform_node_get_transform (node); + GskTransformCategory category = gsk_transform_get_category (transform); + + if (category >= GSK_TRANSFORM_CATEGORY_2D_TRANSLATE) + { + if (add_new_node (renderer, node, BROADWAY_NODE_TRANSLATE)) { + float dx, dy; + gsk_transform_to_translate (transform, &dx, &dy); + + add_xy (nodes, dx, dy, offset_x, offset_y); + gsk_broadway_renderer_add_node (renderer, + gsk_transform_node_get_child (node), + 0, 0); + } + } + else + { + /* Fallback to texture for now */ + break; + } } return; + case GSK_DEBUG_NODE: + if (add_new_node (renderer, node, BROADWAY_NODE_DEBUG)) + { + const char *message = gsk_debug_node_get_message (node); + add_string (nodes, message); + gsk_broadway_renderer_add_node (renderer, + gsk_debug_node_get_child (node), offset_x, offset_y); + } + return; + /* Generic nodes */ case GSK_CONTAINER_NODE: - { - guint i; + if (add_new_node (renderer, node, BROADWAY_NODE_CONTAINER)) + { + guint i; - add_uint32 (nodes, BROADWAY_NODE_CONTAINER); - add_uint32 (nodes, gsk_container_node_get_n_children (node)); - - for (i = 0; i < gsk_container_node_get_n_children (node); i++) - gsk_broadway_renderer_add_node (renderer, nodes, node_textures, - gsk_container_node_get_child (node, i), offset_x, offset_y); - } - return; - - case GSK_DEBUG_NODE: - gsk_broadway_renderer_add_node (renderer, nodes, node_textures, - gsk_debug_node_get_child (node), offset_x, offset_y); + add_uint32 (nodes, gsk_container_node_get_n_children (node)); + for (i = 0; i < gsk_container_node_get_n_children (node); i++) + gsk_broadway_renderer_add_node (renderer, + gsk_container_node_get_child (node, i), offset_x, offset_y); + } return; case GSK_COLOR_MATRIX_NODE: case GSK_TEXT_NODE: case GSK_REPEATING_LINEAR_GRADIENT_NODE: - case GSK_TRANSFORM_NODE: case GSK_REPEAT_NODE: case GSK_BLEND_NODE: case GSK_CROSS_FADE_NODE: @@ -665,32 +563,33 @@ gsk_broadway_renderer_add_node (GskRenderer *renderer, break; /* Fallback */ } - { - GdkTexture *texture; - guint32 texture_id; - float t_off_x = 0, t_off_y = 0; + if (add_new_node (renderer, node, BROADWAY_NODE_TEXTURE)) + { + GdkTexture *texture; + cairo_surface_t *surface; + cairo_t *cr; + guint32 texture_id; + int x = floorf (node->bounds.origin.x); + int y = floorf (node->bounds.origin.y); + int width = ceil (node->bounds.origin.x + node->bounds.size.width) - x; + int height = ceil (node->bounds.origin.y + node->bounds.size.height) - y; - texture = node_cache_lookup (node, &t_off_x, &t_off_y); + surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height); + cr = cairo_create (surface); + cairo_translate (cr, -x, -y); + gsk_render_node_draw (node, cr); + cairo_destroy (cr); - if (!texture) - { - texture = node_texture_fallback (node, &t_off_x, &t_off_y); -#if 0 - g_print ("Fallback %p for %s\n", texture, node->node_class->type_name); -#endif + texture = gdk_texture_new_for_surface (surface); + g_ptr_array_add (self->node_textures, texture); /* Transfers ownership to node_textures */ - node_cache_store (node, texture, t_off_x, t_off_y); - } - - g_ptr_array_add (node_textures, texture); /* Transfers ownership to node_textures */ - texture_id = gdk_broadway_display_ensure_texture (display, texture); - add_uint32 (nodes, BROADWAY_NODE_TEXTURE); - add_float (nodes, node->bounds.origin.x + t_off_x - offset_x); - add_float (nodes, node->bounds.origin.y + t_off_y - offset_y); - add_float (nodes, gdk_texture_get_width (texture)); - add_float (nodes, gdk_texture_get_height (texture)); - add_uint32 (nodes, texture_id); - } + texture_id = gdk_broadway_display_ensure_texture (display, texture); + add_float (nodes, x - offset_x); + add_float (nodes, y - offset_y); + add_float (nodes, gdk_texture_get_width (texture)); + add_float (nodes, gdk_texture_get_height (texture)); + add_uint32 (nodes, texture_id); + } } static void @@ -700,9 +599,40 @@ gsk_broadway_renderer_render (GskRenderer *renderer, { GskBroadwayRenderer *self = GSK_BROADWAY_RENDERER (renderer); + self->node_lookup = g_hash_table_new (g_direct_hash, g_direct_equal); + gdk_draw_context_begin_frame (GDK_DRAW_CONTEXT (self->draw_context), update_area); - gsk_broadway_renderer_add_node (renderer, self->draw_context->nodes, self->draw_context->node_textures, root, 0, 0); + + /* These are owned by the draw context between begin and end, but + cache them here for easier access during the render */ + self->nodes = self->draw_context->nodes; + self->node_textures = self->draw_context->node_textures; + + gsk_broadway_renderer_add_node (renderer, root, 0, 0); + + self->nodes = NULL; + self->node_textures = NULL; + gdk_draw_context_end_frame (GDK_DRAW_CONTEXT (self->draw_context)); + + if (self->last_node_lookup) + g_hash_table_unref (self->last_node_lookup); + self->last_node_lookup = self->node_lookup; + self->node_lookup = NULL; + + if (self->last_root) + gsk_render_node_unref (self->last_root); + self->last_root = gsk_render_node_ref (root); + + if (self->next_node_id > G_MAXUINT32 / 2) + { + /* We're "near" a wrap of the ids, lets avoid reusing any of + * these nodes next frame, then we can reset the id counter + * without risk of any old nodes sticking around and conflicting. */ + + g_hash_table_remove_all (self->last_node_lookup); + self->next_node_id = 0; + } } static void