From 8b4290a701df62d7092cd0db5817174e9c9b5ea4 Mon Sep 17 00:00:00 2001 From: Tom Hoenderdos Date: Thu, 1 Oct 2026 20:08:42 +0200 Subject: [PATCH 1/3] sdl: Move display list damage tracking to its own file Move the display list comparison and damage rectangle helpers from display.c to damage.c, unchanged apart from the names of the two functions display.c calls, now damage_diff() and damage_clip(). They don't use SDL, so they can be built and tested on their own. Co-Authored-By: Claude Opus 5.5 (1M context) Signed-off-by: Tom Hoenderdos --- sdl_display/CMakeLists.txt | 2 +- sdl_display/damage.c | 141 +++++++++++++++++++++++++++++++++++++ sdl_display/damage.h | 41 +++++++++++ sdl_display/display.c | 132 +--------------------------------- 4 files changed, 186 insertions(+), 130 deletions(-) create mode 100644 sdl_display/damage.c create mode 100644 sdl_display/damage.h diff --git a/sdl_display/CMakeLists.txt b/sdl_display/CMakeLists.txt index 0d95983..0b06817 100644 --- a/sdl_display/CMakeLists.txt +++ b/sdl_display/CMakeLists.txt @@ -43,7 +43,7 @@ endif() set(CMAKE_SHARED_LIBRARY_PREFIX "") -add_library(avm_display_port_driver SHARED display.c ../ufontlib.c ../image_helpers.c ../spng.c ../font_data.c ../display_items.c ../display_message.c) +add_library(avm_display_port_driver SHARED display.c damage.c ../ufontlib.c ../image_helpers.c ../spng.c ../font_data.c ../display_items.c ../display_message.c) if (AVM_DISABLE_SMP) target_compile_definitions(avm_display_port_driver PUBLIC AVM_NO_SMP) diff --git a/sdl_display/damage.c b/sdl_display/damage.c new file mode 100644 index 0000000..4beb07a --- /dev/null +++ b/sdl_display/damage.c @@ -0,0 +1,141 @@ +/* + * This file is part of AtomGL. + * + * Copyright 2026 Tom Hoenderdos + * + * Licensed 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. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "damage.h" + +#include + +static inline int int_min(int a, int b) +{ + return (a > b) ? b : a; +} + +static inline int int_max(int a, int b) +{ + return (a > b) ? a : b; +} + +static bool cmp_display_item(BaseDisplayItem *a, BaseDisplayItem *b) +{ + if (a->primitive != b->primitive || a->x != b->x || a->y != b->y || + a->width != b->width || a->height != b->height || a->brcolor != b->brcolor) { + return false; + } + + switch (a->primitive) { + case PrimitiveImage: + return a->data.image_data.pix == b->data.image_data.pix; + + case PrimitiveRect: + return true; + + case PrimitiveText: + return (a->data.text_data.fgcolor == b->data.text_data.fgcolor) && + !strcmp(a->data.text_data.text, b->data.text_data.text); + + case PrimitiveScaledCroppedImage: + return (a->data.image_data.pix == b->data.image_data.pix) && + (a->x_scale == b->x_scale) && (a->y_scale == b->y_scale) && + (a->source_x == b->source_x) && (a->source_y == b->source_y); + + default: { + return true; + } + } +} + +static void update_damaged_area(struct Rectangle *area, const struct Rectangle *damage) +{ + if (area->valid) { + area->x = int_min(area->x, damage->x); + area->y = int_min(area->y, damage->y); + area->width = int_max(area->x + area->width, damage->x + damage->width) - area->x; + area->height = int_max(area->y + area->height, damage->y + damage->height) - area->y; + } else { + area->x = damage->x; + area->y = damage->y; + area->width = damage->width; + area->height = damage->height; + area->valid = true; + } +} + +void damage_clip(struct Rectangle *rectangle, const struct Rectangle *clip_region) +{ + rectangle->x = int_max(rectangle->x, clip_region->x); + rectangle->y = int_max(rectangle->y, clip_region->y); + rectangle->width = int_min(rectangle->x + rectangle->width, clip_region->x + clip_region->width) - rectangle->x; + rectangle->height = int_min(rectangle->y + rectangle->height, clip_region->y + clip_region->height) - rectangle->y; +} + +void damage_diff(BaseDisplayItem *orig, int orig_len, BaseDisplayItem *new, int new_len, struct Rectangle *damaged) +{ + if (orig_len == 0) { + for (int i = 0; i < new_len; i++) { + struct Rectangle irect = { + .x = new[i].x, + .y = new[i].y, + .width = new[i].width, + .height = new[i].height, + .valid = true + }; + update_damaged_area(damaged, &irect); + } + return; + } + + int j = 0; + + for (int i = 0; i < new_len; i++) { + if (cmp_display_item(&new[i], &orig[j])) { + j++; + } else { + bool found = false; + for (int k = j + 1; k < orig_len; k++) { + if (cmp_display_item(&new[i], &orig[k])) { + for (int l = k - j; l < k; l++) { + struct Rectangle irect = { + .x = orig[l].x, + .y = orig[l].y, + .width = orig[l].width, + .height = orig[l].height, + .valid = true + }; + update_damaged_area(damaged, &irect); + } + + j = k + 1; + found = true; + break; + } + } + if (!found) { + struct Rectangle irect = { + .x = new[i].x, + .y = new[i].y, + .width = new[i].width, + .height = new[i].height, + .valid = true + }; + update_damaged_area(damaged, &irect); + } + } + } +} diff --git a/sdl_display/damage.h b/sdl_display/damage.h new file mode 100644 index 0000000..e5ca826 --- /dev/null +++ b/sdl_display/damage.h @@ -0,0 +1,41 @@ +/* + * This file is part of AtomGL. + * + * Copyright 2026 Tom Hoenderdos + * + * Licensed 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. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _DAMAGE_H_ +#define _DAMAGE_H_ + +#include + +#include "../display_items.h" + +struct Rectangle +{ + int x; + int y; + int width; + int height; + bool valid; +}; + +void damage_diff(BaseDisplayItem *orig, int orig_len, BaseDisplayItem *new, int new_len, + struct Rectangle *damaged); +void damage_clip(struct Rectangle *rectangle, const struct Rectangle *clip_region); + +#endif diff --git a/sdl_display/display.c b/sdl_display/display.c index c6760d2..3162497 100644 --- a/sdl_display/display.c +++ b/sdl_display/display.c @@ -42,6 +42,7 @@ #include "../display_message.h" #include "../font_data.h" #include "../image_helpers.h" +#include "damage.h" struct DisplayOpts { @@ -64,15 +65,6 @@ struct MouseEvent int y; }; -struct Rectangle -{ - int x; - int y; - int width; - int height; - bool valid; -}; - static term keyboard_pid; static struct timespec ts0; Context *the_ctx; @@ -107,124 +99,6 @@ static void destroy_message(Message *m, GlobalContext *global) END_WITH_STACK_HEAP(temp_heap, global); } -static inline int int_min(int a, int b) -{ - return (a > b) ? b : a; -} - -static inline int int_max(int a, int b) -{ - return (a > b) ? a : b; -} - -static bool cmp_display_item(BaseDisplayItem *a, BaseDisplayItem *b) -{ - if (a->primitive != b->primitive || a->x != b->x || a->y != b->y || - a->width != b->width || a->height != b->height || a->brcolor != b->brcolor) { - return false; - } - - switch (a->primitive) { - case PrimitiveImage: - return a->data.image_data.pix == b->data.image_data.pix; - - case PrimitiveRect: - return true; - - case PrimitiveText: - return (a->data.text_data.fgcolor == b->data.text_data.fgcolor) && - !strcmp(a->data.text_data.text, b->data.text_data.text); - - case PrimitiveScaledCroppedImage: - return (a->data.image_data.pix == b->data.image_data.pix) && - (a->x_scale == b->x_scale) && (a->y_scale == b->y_scale) && - (a->source_x == b->source_x) && (a->source_y == b->source_y); - - default: { - return true; - } - } -} - -static void update_damaged_area(struct Rectangle *area, const struct Rectangle *damage) -{ - if (area->valid) { - area->x = int_min(area->x, damage->x); - area->y = int_min(area->y, damage->y); - area->width = int_max(area->x + area->width, damage->x + damage->width) - area->x; - area->height = int_max(area->y + area->height, damage->y + damage->height) - area->y; - } else { - area->x = damage->x; - area->y = damage->y; - area->width = damage->width; - area->height = damage->height; - area->valid = true; - } -} - -static void clip_rectangle(struct Rectangle *rectangle, const struct Rectangle *clip_region) -{ - rectangle->x = int_max(rectangle->x, clip_region->x); - rectangle->y = int_max(rectangle->y, clip_region->y); - rectangle->width = int_min(rectangle->x + rectangle->width, clip_region->x + clip_region->width) - rectangle->x; - rectangle->height = int_min(rectangle->y + rectangle->height, clip_region->y + clip_region->height) - rectangle->y; -} - -static void dumb_diff(BaseDisplayItem *orig, int orig_len, BaseDisplayItem *new, int new_len, struct Rectangle *damaged) -{ - if (orig_len == 0) { - for (int i = 0; i < new_len; i++) { - struct Rectangle irect = { - .x = new[i].x, - .y = new[i].y, - .width = new[i].width, - .height = new[i].height, - .valid = true - }; - update_damaged_area(damaged, &irect); - } - return; - } - - int j = 0; - - for (int i = 0; i < new_len; i++) { - if (cmp_display_item(&new[i], &orig[j])) { - j++; - } else { - bool found = false; - for (int k = j + 1; k < orig_len; k++) { - if (cmp_display_item(&new[i], &orig[k])) { - for (int l = k - j; l < k; l++) { - struct Rectangle irect = { - .x = orig[l].x, - .y = orig[l].y, - .width = orig[l].width, - .height = orig[l].height, - .valid = true - }; - update_damaged_area(damaged, &irect); - } - - j = k + 1; - found = true; - break; - } - } - if (!found) { - struct Rectangle irect = { - .x = new[i].x, - .y = new[i].y, - .width = new[i].width, - .height = new[i].height, - .valid = true - }; - update_damaged_area(damaged, &irect); - } - } - } -} - static inline Uint32 uint32_color_to_surface(struct Screen *screen, uint32_t color) { return SDL_MapRGB(screen->format, (color >> 24) & 0xFF, (color >> 16) & 0xFF, (color >> 8) & 0xFF); @@ -474,7 +348,7 @@ static bool do_update(Context *ctx, term display_list) struct Rectangle damaged; damaged.valid = false; - dumb_diff(prev_items, prev_items_len, items, len, &damaged); + damage_diff(prev_items, prev_items_len, items, len, &damaged); if (prev_message) { display_items_delete(prev_items, prev_items_len); destroy_message(prev_message, ctx->global); @@ -494,7 +368,7 @@ static bool do_update(Context *ctx, term display_list) .height = screen->h, .valid = true }; - clip_rectangle(&damaged, &screen_rect); + damage_clip(&damaged, &screen_rect); // BUG: damage area is not correct // WORKAROUND: always damage the whole screen to force full redraw From 99771cb7f1caf1e9ed90d889f5bc9a5264df6d63 Mon Sep 17 00:00:00 2001 From: Tom Hoenderdos Date: Thu, 1 Oct 2026 20:10:30 +0200 Subject: [PATCH 2/3] sdl: Fix display list damage comparison The SDL display only redraws the areas where the new display list differs from the previous one, but two bugs made that comparison wrong. cmp_display_item() compared scaled_cropped_image items through data.image_data.pix. That union member overlaps the width and height of data.image_data_with_size, not its pixel pointer, so two items showing different images of the same size compared equal and the change was never drawn. The items are now compared by their own pixel pointer, width and height. damage_diff() compared each new item with orig[j] without checking j, so a new list longer than the previous one read past the end of the previous item array. Add tests/damage, a host test of damage.c under ASan and UBSan. Without this fix it reports no damage for the changed image and ASan aborts on the read past the previous list. Build and run with: cmake -S tests/damage -B build/damage \ -DLIBATOMVM_INCLUDE_PATH=/src/libAtomVM cmake --build build/damage && build/damage/test_damage Co-Authored-By: Claude Opus 5.5 (1M context) Signed-off-by: Tom Hoenderdos --- sdl_display/damage.c | 10 ++- tests/damage/CMakeLists.txt | 43 +++++++++++ tests/damage/test_damage.c | 144 ++++++++++++++++++++++++++++++++++++ 3 files changed, 193 insertions(+), 4 deletions(-) create mode 100644 tests/damage/CMakeLists.txt create mode 100644 tests/damage/test_damage.c diff --git a/sdl_display/damage.c b/sdl_display/damage.c index 4beb07a..c4c5a2e 100644 --- a/sdl_display/damage.c +++ b/sdl_display/damage.c @@ -51,9 +51,11 @@ static bool cmp_display_item(BaseDisplayItem *a, BaseDisplayItem *b) !strcmp(a->data.text_data.text, b->data.text_data.text); case PrimitiveScaledCroppedImage: - return (a->data.image_data.pix == b->data.image_data.pix) && - (a->x_scale == b->x_scale) && (a->y_scale == b->y_scale) && - (a->source_x == b->source_x) && (a->source_y == b->source_y); + return (a->data.image_data_with_size.pix == b->data.image_data_with_size.pix) + && (a->data.image_data_with_size.width == b->data.image_data_with_size.width) + && (a->data.image_data_with_size.height == b->data.image_data_with_size.height) + && (a->x_scale == b->x_scale) && (a->y_scale == b->y_scale) + && (a->source_x == b->source_x) && (a->source_y == b->source_y); default: { return true; @@ -104,7 +106,7 @@ void damage_diff(BaseDisplayItem *orig, int orig_len, BaseDisplayItem *new, int int j = 0; for (int i = 0; i < new_len; i++) { - if (cmp_display_item(&new[i], &orig[j])) { + if (j < orig_len && cmp_display_item(&new[i], &orig[j])) { j++; } else { bool found = false; diff --git a/tests/damage/CMakeLists.txt b/tests/damage/CMakeLists.txt new file mode 100644 index 0000000..0f9cb27 --- /dev/null +++ b/tests/damage/CMakeLists.txt @@ -0,0 +1,43 @@ +# +# This file is part of AtomGL. +# +# Copyright 2026 Tom Hoenderdos +# +# Licensed 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. +# +# SPDX-License-Identifier: Apache-2.0 +# + +cmake_minimum_required (VERSION 3.13) +project (atomgl_damage_tests C) + +set(CMAKE_C_STANDARD 11) +if (NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Debug) +endif() + +if (NOT LIBATOMVM_INCLUDE_PATH) + message(FATAL_ERROR "Set LIBATOMVM_INCLUDE_PATH to AtomVM's src/libAtomVM (headers only, nothing is linked)") +endif() + +option(DAMAGE_SANITIZE "Build with ASan and UBSan, aborting on the first report" ON) + +add_executable(test_damage test_damage.c ../../sdl_display/damage.c) +target_include_directories(test_damage PRIVATE ../../sdl_display ${LIBATOMVM_INCLUDE_PATH}) +target_compile_definitions(test_damage PRIVATE HAVE_ATOMIC) +target_compile_options(test_damage PRIVATE -Wall -Wextra -Wno-unused-parameter) +if (DAMAGE_SANITIZE) + set(SANITIZE_FLAGS -fsanitize=address,undefined -fno-sanitize-recover=all -fno-omit-frame-pointer) + target_compile_options(test_damage PRIVATE ${SANITIZE_FLAGS}) + target_link_options(test_damage PRIVATE ${SANITIZE_FLAGS}) +endif() diff --git a/tests/damage/test_damage.c b/tests/damage/test_damage.c new file mode 100644 index 0000000..f055a8d --- /dev/null +++ b/tests/damage/test_damage.c @@ -0,0 +1,144 @@ +/* + * This file is part of AtomGL. + * + * Copyright 2026 Tom Hoenderdos + * + * Licensed 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. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include "damage.h" + +static int failures; +static int checks; + +#define CHECK(cond, ...) \ + do { \ + checks++; \ + if (!(cond)) { \ + failures++; \ + fprintf(stderr, "FAIL %s:%d: ", __FILE__, __LINE__); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, "\n"); \ + } \ + } while (0) + +static uint32_t pixels_a[4 * 2]; +static uint32_t pixels_b[4 * 2]; + +static BaseDisplayItem rect(int x, int y, int width, int height) +{ + BaseDisplayItem item; + memset(&item, 0, sizeof(item)); + item.primitive = PrimitiveRect; + item.x = x; + item.y = y; + item.width = width; + item.height = height; + item.brcolor = 0xFF0000FF; + + return item; +} + +static BaseDisplayItem scaled_cropped_image(const uint32_t *pixels) +{ + BaseDisplayItem item; + memset(&item, 0, sizeof(item)); + item.primitive = PrimitiveScaledCroppedImage; + item.x = 10; + item.y = 20; + item.width = 8; + item.height = 4; + item.x_scale = 2; + item.y_scale = 2; + item.data.image_data_with_size.width = 4; + item.data.image_data_with_size.height = 2; + item.data.image_data_with_size.pix = (const char *) pixels; + + return item; +} + +static BaseDisplayItem *items_new(const BaseDisplayItem items[], int items_len) +{ + BaseDisplayItem *copy = malloc(sizeof(BaseDisplayItem) * items_len); + if (copy == NULL) { + abort(); + } + memcpy(copy, items, sizeof(BaseDisplayItem) * items_len); + + return copy; +} + +static struct Rectangle diff(const BaseDisplayItem orig[], int orig_len, + const BaseDisplayItem new[], int new_len) +{ + BaseDisplayItem *orig_copy = items_new(orig, orig_len); + BaseDisplayItem *new_copy = items_new(new, new_len); + struct Rectangle damaged; + memset(&damaged, 0, sizeof(damaged)); + damage_diff(orig_copy, orig_len, new_copy, new_len, &damaged); + free(orig_copy); + free(new_copy); + + return damaged; +} + +static void test_same_list(void) +{ + BaseDisplayItem items[] = { rect(0, 0, 4, 4), scaled_cropped_image(pixels_a) }; + struct Rectangle damaged = diff(items, 2, items, 2); + CHECK(!damaged.valid, "same list: damage (%d, %d, %d, %d)", damaged.x, damaged.y, + damaged.width, damaged.height); +} + +static void test_scaled_cropped_image_changed(void) +{ + BaseDisplayItem orig[] = { scaled_cropped_image(pixels_a) }; + BaseDisplayItem new[] = { scaled_cropped_image(pixels_b) }; + struct Rectangle damaged = diff(orig, 1, new, 1); + CHECK(damaged.valid && damaged.x == 10 && damaged.y == 20 && damaged.width == 8 + && damaged.height == 4, + "other image of the same size: valid %d damage (%d, %d, %d, %d)", damaged.valid, + damaged.x, damaged.y, damaged.width, damaged.height); +} + +static void test_longer_list(void) +{ + BaseDisplayItem orig[] = { rect(0, 0, 4, 4) }; + BaseDisplayItem new[] = { rect(0, 0, 4, 4), rect(5, 6, 7, 8) }; + struct Rectangle damaged = diff(orig, 1, new, 2); + CHECK(damaged.valid && damaged.x == 5 && damaged.y == 6 && damaged.width == 7 + && damaged.height == 8, + "item added at the end: valid %d damage (%d, %d, %d, %d)", damaged.valid, damaged.x, + damaged.y, damaged.width, damaged.height); +} + +int main(void) +{ + test_same_list(); + test_scaled_cropped_image_changed(); + test_longer_list(); + + if (failures) { + fprintf(stderr, "%d of %d checks failed\n", failures, checks); + return 1; + } + printf("all %d damage checks passed\n", checks); + + return 0; +} From 64a21530eb3ac75be65780389999a5e3284b793c Mon Sep 17 00:00:00 2001 From: Tom Hoenderdos Date: Thu, 1 Oct 2026 20:22:02 +0200 Subject: [PATCH 3/3] sdl: Fix damage areas of removed and moved items Even with the comparison fixed, the damage area could miss pixels that had to be redrawn: - When a new item matched an old one further down the previous list, the skipped old items were damaged from index k - j instead of j, so removing the first item of a list damaged nothing. - Old items after the last match were never damaged, so removing the last item or moving an item left its old pixels on screen. - update_damaged_area() and damage_clip() overwrote x and y before computing the right and bottom edges from them. Merging a damage rectangle left of or above the current one shrank the area, and clipping one that started off screen kept its full size. display.c still redraws the whole screen after computing the damage, so none of this was visible yet. Co-Authored-By: Claude Opus 5.5 (1M context) Signed-off-by: Tom Hoenderdos --- sdl_display/damage.c | 25 +++++++++++++---- tests/damage/test_damage.c | 55 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 5 deletions(-) diff --git a/sdl_display/damage.c b/sdl_display/damage.c index c4c5a2e..5e3409a 100644 --- a/sdl_display/damage.c +++ b/sdl_display/damage.c @@ -66,10 +66,12 @@ static bool cmp_display_item(BaseDisplayItem *a, BaseDisplayItem *b) static void update_damaged_area(struct Rectangle *area, const struct Rectangle *damage) { if (area->valid) { + int right = int_max(area->x + area->width, damage->x + damage->width); + int bottom = int_max(area->y + area->height, damage->y + damage->height); area->x = int_min(area->x, damage->x); area->y = int_min(area->y, damage->y); - area->width = int_max(area->x + area->width, damage->x + damage->width) - area->x; - area->height = int_max(area->y + area->height, damage->y + damage->height) - area->y; + area->width = right - area->x; + area->height = bottom - area->y; } else { area->x = damage->x; area->y = damage->y; @@ -81,10 +83,12 @@ static void update_damaged_area(struct Rectangle *area, const struct Rectangle * void damage_clip(struct Rectangle *rectangle, const struct Rectangle *clip_region) { + int right = int_min(rectangle->x + rectangle->width, clip_region->x + clip_region->width); + int bottom = int_min(rectangle->y + rectangle->height, clip_region->y + clip_region->height); rectangle->x = int_max(rectangle->x, clip_region->x); rectangle->y = int_max(rectangle->y, clip_region->y); - rectangle->width = int_min(rectangle->x + rectangle->width, clip_region->x + clip_region->width) - rectangle->x; - rectangle->height = int_min(rectangle->y + rectangle->height, clip_region->y + clip_region->height) - rectangle->y; + rectangle->width = right - rectangle->x; + rectangle->height = bottom - rectangle->y; } void damage_diff(BaseDisplayItem *orig, int orig_len, BaseDisplayItem *new, int new_len, struct Rectangle *damaged) @@ -112,7 +116,7 @@ void damage_diff(BaseDisplayItem *orig, int orig_len, BaseDisplayItem *new, int bool found = false; for (int k = j + 1; k < orig_len; k++) { if (cmp_display_item(&new[i], &orig[k])) { - for (int l = k - j; l < k; l++) { + for (int l = j; l < k; l++) { struct Rectangle irect = { .x = orig[l].x, .y = orig[l].y, @@ -140,4 +144,15 @@ void damage_diff(BaseDisplayItem *orig, int orig_len, BaseDisplayItem *new, int } } } + + for (int l = j; l < orig_len; l++) { + struct Rectangle irect = { + .x = orig[l].x, + .y = orig[l].y, + .width = orig[l].width, + .height = orig[l].height, + .valid = true + }; + update_damaged_area(damaged, &irect); + } } diff --git a/tests/damage/test_damage.c b/tests/damage/test_damage.c index f055a8d..5259997 100644 --- a/tests/damage/test_damage.c +++ b/tests/damage/test_damage.c @@ -128,11 +128,66 @@ static void test_longer_list(void) damaged.y, damaged.width, damaged.height); } +static void check_damage(const char *name, struct Rectangle damaged, int x, int y, int width, + int height) +{ + CHECK(damaged.valid && damaged.x == x && damaged.y == y && damaged.width == width + && damaged.height == height, + "%s: valid %d damage (%d, %d, %d, %d), expected (%d, %d, %d, %d)", name, damaged.valid, + damaged.x, damaged.y, damaged.width, damaged.height, x, y, width, height); +} + +static void test_first_item_removed(void) +{ + BaseDisplayItem orig[] = { rect(0, 0, 4, 4), rect(10, 10, 2, 2) }; + BaseDisplayItem new[] = { rect(10, 10, 2, 2) }; + check_damage("first item removed", diff(orig, 2, new, 1), 0, 0, 4, 4); +} + +static void test_middle_items_removed(void) +{ + BaseDisplayItem orig[] = { rect(0, 0, 1, 1), rect(20, 20, 2, 2), rect(30, 30, 3, 3), + rect(40, 40, 1, 1) }; + BaseDisplayItem new[] = { rect(0, 0, 1, 1), rect(40, 40, 1, 1) }; + check_damage("middle items removed", diff(orig, 4, new, 2), 20, 20, 13, 13); +} + +static void test_last_item_removed(void) +{ + BaseDisplayItem orig[] = { rect(0, 0, 4, 4), rect(10, 10, 2, 2) }; + BaseDisplayItem new[] = { rect(0, 0, 4, 4) }; + check_damage("last item removed", diff(orig, 2, new, 1), 10, 10, 2, 2); +} + +static void test_item_moved(void) +{ + BaseDisplayItem orig[] = { rect(0, 0, 4, 4) }; + BaseDisplayItem new[] = { rect(10, 10, 4, 4) }; + check_damage("item moved", diff(orig, 1, new, 1), 0, 0, 14, 14); +} + +static void test_clip(void) +{ + struct Rectangle screen = { .x = 0, .y = 0, .width = 320, .height = 240, .valid = true }; + struct Rectangle damaged = { .x = -5, .y = -8, .width = 10, .height = 20, .valid = true }; + damage_clip(&damaged, &screen); + check_damage("clipped at the top left", damaged, 0, 0, 5, 12); + + damaged = (struct Rectangle) { .x = 310, .y = 230, .width = 20, .height = 20, .valid = true }; + damage_clip(&damaged, &screen); + check_damage("clipped at the bottom right", damaged, 310, 230, 10, 10); +} + int main(void) { test_same_list(); test_scaled_cropped_image_changed(); test_longer_list(); + test_first_item_removed(); + test_middle_items_removed(); + test_last_item_removed(); + test_item_moved(); + test_clip(); if (failures) { fprintf(stderr, "%d of %d checks failed\n", failures, checks);