| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| #include "display-plan.h" |
| #include "display-priv.h" |
| #include "guacamole/assert.h" |
| #include "guacamole/client.h" |
| #include "guacamole/display.h" |
| #include "guacamole/fifo.h" |
| #include "guacamole/mem.h" |
| #include "guacamole/protocol.h" |
| #include "guacamole/socket.h" |
| #include "guacamole/timestamp.h" |
| |
| #include <string.h> |
| #include <cairo/cairo.h> |
| |
| /** |
| * Updates the dirty rect in the given cell to note that a horizontal line of |
| * image data at the given location and having the given width has changed |
| * since the last frame. A provided counter of the overall number of changed |
| * cells is updated accordingly. |
| * |
| * @param layer |
| * The layer that changed. |
| * |
| * @param cell |
| * The cell containing the line of image data that changed. |
| * |
| * @param count |
| * A pointer to a counter that contains the current number of cells that |
| * have been marked as having changed since the last frame. |
| * |
| * @param x |
| * The X coordinate of the leftmost pixel of the horizontal line. |
| * |
| * @param y |
| * The Y coordinate of the leftmost pixel of the horizontal line. |
| * |
| * @param width |
| * The width of the line, in pixels. |
| */ |
| static void guac_display_plan_mark_dirty(guac_display_layer* layer, |
| guac_display_layer_cell* cell, size_t* count, int x, int y, |
| int width) { |
| |
| if (!cell->dirty_size) { |
| guac_rect_init(&cell->dirty, x, y, width, 1); |
| cell->dirty_size = width; |
| (*count)++; |
| } |
| |
| else { |
| guac_rect dirty; |
| guac_rect_init(&dirty, x, y, width, 1); |
| guac_rect_extend(&cell->dirty, &dirty); |
| cell->dirty_size += width; |
| } |
| |
| } |
| |
| /** |
| * Variant of memcmp() which specifically compares series of 32-bit quantities |
| * and determines the overall location and length of the differences in the two |
| * provided buffers. The length and location determined are the length and |
| * location of the smallest contiguous series of 32-bit quantities that differ |
| * between the buffers. |
| * |
| * @param buffer_a |
| * The first buffer to compare. |
| * |
| * @param buffer_b |
| * The buffer to compare with buffer_a. |
| * |
| * @param count |
| * The number of 32-bit quantities in each buffer. |
| * |
| * @param pos |
| * A pointer to a size_t that should receive the offset of the difference, |
| * if the two buffers turn out to contain different data. The value of the |
| * size_t will only be modified if at least one difference is found. |
| * |
| * @return |
| * The number of 32-bit quantities after and including the offset returned |
| * via pos that are different between buffer_a and buffer_b, or zero if |
| * there are no such differences. |
| */ |
| static size_t guac_display_memcmp(const uint32_t* restrict buffer_a, |
| const uint32_t* restrict buffer_b, size_t count, size_t* pos) { |
| |
| /* Locate first difference between the buffers, if any */ |
| size_t first = 0; |
| while (first < count) { |
| |
| if (*(buffer_a++) != *(buffer_b++)) |
| break; |
| |
| first++; |
| |
| } |
| |
| /* If we reached the end without finding any differences, no need to search |
| * further - the buffers are identical */ |
| if (first >= count) |
| return 0; |
| |
| /* Search through all remaining values in the buffers for the last |
| * difference (which may be identical to the first) */ |
| size_t last = first; |
| size_t offset = first + 1; |
| while (offset < count) { |
| |
| if (*(buffer_a++) != *(buffer_b++)) |
| last = offset; |
| |
| offset++; |
| |
| } |
| |
| /* Final difference found - provide caller with the starting offset and |
| * length (in 32-bit quantities) of differences */ |
| *pos = first; |
| return last - first + 1; |
| |
| } |
| |
| guac_display_plan* PFW_LFR_guac_display_plan_create(guac_display* display) { |
| |
| guac_display_layer* current; |
| guac_timestamp frame_end = guac_timestamp_current(); |
| size_t op_count = 0; |
| |
| /* Loop through each layer, searching for modified regions */ |
| current = display->pending_frame.layers; |
| while (current != NULL) { |
| |
| /* Skip processing any layers whose buffers have been replaced with |
| * NULL (this is intentionally allowed to ensure references to external |
| * buffers can be safely removed if necessary, even before guac_display |
| * is freed) */ |
| if (current->pending_frame.buffer == NULL) { |
| GUAC_ASSERT(current->pending_frame.buffer_is_external); |
| current = current->pending_frame.next; |
| continue; |
| } |
| |
| /* Check only within layer dirty region, skipping the layer if |
| * unmodified. This pass should reset and refine that region, but |
| * otherwise rely on proper reporting of modified regions by callers of |
| * the open/close layer functions. */ |
| guac_rect dirty = current->pending_frame.dirty; |
| if (guac_rect_is_empty(&dirty)) { |
| current = current->pending_frame.next; |
| continue; |
| } |
| |
| /* Flush any outstanding Cairo operations before directly accessing buffer */ |
| guac_display_layer_cairo_context* cairo_context = &(current->pending_frame_cairo_context); |
| if (cairo_context->surface != NULL) |
| cairo_surface_flush(cairo_context->surface); |
| |
| /* Re-align the dirty rect with nearest multiple of 64 to ensure each |
| * step of the dirty rect refinement loop starts at the topmost |
| * boundary of a cell */ |
| guac_rect_align(&dirty, GUAC_DISPLAY_CELL_SIZE_EXPONENT); |
| |
| guac_rect pending_frame_bounds = { |
| .left = 0, |
| .top = 0, |
| .right = current->pending_frame.width, |
| .bottom = current->pending_frame.height |
| }; |
| |
| /* Limit size of dirty rect by bounds of backing surface for pending |
| * frame ONLY (bounds checks against the last frame are performed |
| * within the loop such that everything outside the bounds of the last |
| * frame is considered dirty) */ |
| guac_rect_constrain(&dirty, &pending_frame_bounds); |
| |
| const unsigned char* flushed_row = GUAC_DISPLAY_LAYER_STATE_CONST_BUFFER(current->last_frame, dirty); |
| unsigned char* buffer_row = GUAC_DISPLAY_LAYER_STATE_MUTABLE_BUFFER(current->pending_frame, dirty); |
| |
| guac_display_layer_cell* cell_row = current->pending_frame_cells |
| + guac_mem_ckd_mul_or_die(dirty.top / GUAC_DISPLAY_CELL_SIZE, current->pending_frame_cells_width) |
| + dirty.left / GUAC_DISPLAY_CELL_SIZE; |
| |
| /* Loop through the rough modified region, refining the dirty rects of |
| * each cell to more accurately contain only what has actually changed |
| * since last frame */ |
| current->pending_frame.dirty = (guac_rect) { 0 }; |
| for (int corner_y = dirty.top; corner_y < dirty.bottom; corner_y += GUAC_DISPLAY_CELL_SIZE) { |
| |
| int height = GUAC_DISPLAY_CELL_SIZE; |
| if (corner_y + height > dirty.bottom) |
| height = dirty.bottom - corner_y; |
| |
| /* Iteration through the pending_frame_cells array and the image |
| * buffer is a bit complex here, as the pending_frame_cells array |
| * contains cells that represent 64x64 regions, while the image |
| * buffers contain absolutely all pixels. The outer loop goes |
| * through just the pending cells, while the following loop goes |
| * through the Y coordinates that make up that cell. */ |
| |
| for (int y_off = 0; y_off < height; y_off++) { |
| |
| /* At this point, we need to loop through the horizontal |
| * dimension, comparing the 64-pixel rows of image data in the |
| * current line (corner_y + y_off) that are in each applicable |
| * cell. We jump forward by one cell for each comparison. */ |
| |
| int y = corner_y + y_off; |
| |
| guac_display_layer_cell* current_cell = cell_row; |
| uint32_t* current_flushed = (uint32_t*) flushed_row; |
| uint32_t* current_buffer = (uint32_t*) buffer_row; |
| for (int corner_x = dirty.left; corner_x < dirty.right; corner_x += GUAC_DISPLAY_CELL_SIZE) { |
| |
| int width = GUAC_DISPLAY_CELL_SIZE; |
| if (corner_x + width > dirty.right) |
| width = dirty.right - corner_x; |
| |
| /* This SHOULD be impossible, as corner_x would need to |
| * somehow be outside the bounds of the dirty rect, which |
| * would have failed the loop condition earlier) */ |
| GUAC_ASSERT(width >= 0); |
| |
| /* Any line that is completely outside the bounds of the |
| * previous frame is dirty (nothing to compare against) */ |
| if (y >= current->last_frame.height || corner_x >= current->last_frame.width) { |
| guac_display_plan_mark_dirty(current, current_cell, &op_count, corner_x, y, width); |
| guac_rect_extend(¤t->pending_frame.dirty, ¤t_cell->dirty); |
| } |
| |
| /* All other regions must be processed further to determine |
| * what portion is dirty */ |
| else { |
| |
| /* Only the pixels that are within the bounds of BOTH |
| * the last_frame and pending_frame are directly |
| * comparable. Others are inherently dirty by virtue of |
| * being outside the bounds of last_frame */ |
| int comparable_width = width; |
| if (corner_x + comparable_width > current->last_frame.width) |
| comparable_width = current->last_frame.width - corner_x; |
| |
| /* It is impossible for this value to be negative |
| * because of the last_frame bounds checks that occur |
| * in the if block prior to this else block */ |
| GUAC_ASSERT(comparable_width >= 0); |
| |
| /* Any region outside the right edge of the previous frame is dirty */ |
| if (width > comparable_width) { |
| guac_display_plan_mark_dirty(current, current_cell, &op_count, corner_x + comparable_width, y, width - comparable_width); |
| guac_rect_extend(¤t->pending_frame.dirty, ¤t_cell->dirty); |
| } |
| |
| /* Mark the relevant region of the cell as dirty if the |
| * current 64-pixel line has changed in any way */ |
| size_t length, pos; |
| if ((length = guac_display_memcmp(current_buffer, current_flushed, comparable_width, &pos)) != 0) { |
| guac_display_plan_mark_dirty(current, current_cell, &op_count, corner_x + pos, y, length); |
| guac_rect_extend(¤t->pending_frame.dirty, ¤t_cell->dirty); |
| } |
| |
| } |
| |
| current_flushed += GUAC_DISPLAY_CELL_SIZE; |
| current_buffer += GUAC_DISPLAY_CELL_SIZE; |
| current_cell++; |
| |
| } |
| |
| flushed_row += current->last_frame.buffer_stride; |
| buffer_row += current->pending_frame.buffer_stride; |
| |
| } |
| |
| cell_row += current->pending_frame_cells_width; |
| |
| } |
| |
| current = current->pending_frame.next; |
| |
| } |
| |
| /* If no layer has been modified, there's no need to create a plan */ |
| if (!op_count) |
| return NULL; |
| |
| guac_display_plan* plan = guac_mem_alloc(sizeof(guac_display_plan)); |
| plan->display = display; |
| plan->frame_end = frame_end; |
| plan->length = op_count; |
| plan->ops = guac_mem_alloc(plan->length, sizeof(guac_display_plan_operation)); |
| |
| /* Convert the dirty rectangles stored in each layer's cells to individual |
| * image operations for later optimization */ |
| size_t added_ops = 0; |
| guac_display_plan_operation* current_op = plan->ops; |
| current = display->pending_frame.layers; |
| while (current != NULL) { |
| |
| guac_display_layer_cell* cell = current->pending_frame_cells; |
| for (int y = 0; y < current->pending_frame_cells_height; y++) { |
| for (int x = 0; x < current->pending_frame_cells_width; x++) { |
| |
| if (cell->dirty_size) { |
| |
| /* The overall number of ops that we try to add via these |
| * nested loops should always exactly align with the |
| * anticipated count produced earlier and therefore not |
| * overrun the ops array at any point unless there is a bug |
| * in the way the original operation count was calculated */ |
| GUAC_ASSERT(added_ops < op_count); |
| |
| current_op->layer = current; |
| current_op->type = GUAC_DISPLAY_PLAN_OPERATION_IMG; |
| current_op->dest = cell->dirty; |
| current_op->dirty_size = cell->dirty_size; |
| current_op->last_frame = cell->last_frame; |
| current_op->current_frame = frame_end; |
| |
| cell->related_op = current_op; |
| cell->dirty_size = 0; |
| cell->last_frame = frame_end; |
| |
| current_op++; |
| added_ops++; |
| |
| } |
| else |
| cell->related_op = NULL; |
| |
| cell++; |
| |
| } |
| } |
| |
| current = current->pending_frame.next; |
| |
| } |
| |
| /* At this point, the number of operations added should exactly match the |
| * predicted quantity */ |
| GUAC_ASSERT(added_ops == op_count); |
| |
| return plan; |
| |
| } |
| |
| void guac_display_plan_free(guac_display_plan* plan) { |
| guac_mem_free(plan->ops); |
| guac_mem_free(plan); |
| } |
| |
| void guac_display_plan_apply(guac_display_plan* plan) { |
| |
| guac_display* display = plan->display; |
| guac_client* client = display->client; |
| guac_display_plan_operation* op = plan->ops; |
| |
| /* Do not allow worker threads to move forward with image encoding until |
| * AFTER the non-image instructions have finished being written */ |
| guac_fifo_lock(&display->ops); |
| |
| /* Immediately send instructions for all updates that do not involve |
| * significant processing (do not involve encoding anything). This allows |
| * us to use the worker threads solely for encoding, reducing contention |
| * between the threads. */ |
| for (int i = 0; i < plan->length; i++) { |
| |
| guac_display_layer* display_layer = op->layer; |
| switch (op->type) { |
| |
| case GUAC_DISPLAY_PLAN_OPERATION_COPY: |
| guac_protocol_send_copy(client->socket, op->src.layer_rect.layer, |
| op->src.layer_rect.rect.left, op->src.layer_rect.rect.top, |
| guac_rect_width(&op->src.layer_rect.rect), guac_rect_height(&op->src.layer_rect.rect), |
| GUAC_COMP_OVER, display_layer->layer, op->dest.left, op->dest.top); |
| break; |
| |
| case GUAC_DISPLAY_PLAN_OPERATION_RECT: |
| |
| guac_protocol_send_rect(client->socket, display_layer->layer, |
| op->dest.left, op->dest.top, guac_rect_width(&op->dest), guac_rect_height(&op->dest)); |
| |
| int alpha = (op->src.color & 0xFF000000) >> 24; |
| int red = (op->src.color & 0x00FF0000) >> 16; |
| int green = (op->src.color & 0x0000FF00) >> 8; |
| int blue = (op->src.color & 0x000000FF); |
| |
| /* Clear before drawing if layer is not opaque (transparency |
| * will not be copied correctly otherwise) */ |
| if (!display_layer->opaque) { |
| guac_protocol_send_cfill(client->socket, GUAC_COMP_ROUT, display_layer->layer, 0x00, 0x00, 0x00, 0xFF); |
| guac_protocol_send_cfill(client->socket, GUAC_COMP_OVER, display_layer->layer, red, green, blue, alpha); |
| } |
| else |
| guac_protocol_send_cfill(client->socket, GUAC_COMP_OVER, display_layer->layer, red, green, blue, 0xFF); |
| |
| break; |
| |
| /* Simply ignore and drop NOP */ |
| case GUAC_DISPLAY_PLAN_OPERATION_NOP: |
| break; |
| |
| /* All other operations should be handled by the workers */ |
| default: |
| guac_fifo_enqueue(&display->ops, op); |
| break; |
| |
| } |
| |
| op++; |
| |
| } |
| |
| guac_fifo_unlock(&display->ops); |
| |
| } |