Improve CanvasState cross-library tests

Several things are accomplished as part of this:
1. The canvas_state_lib build target is brought back so that we can
   actually test the canvas state sharing across library boundaries.
2. The canvas state helper functions are updated to work with DLLs
   (confirmed tests pass on Windows in cross library mode)
3. The tests now always run, and the cross-state define only changes
   what version of the helper functions are used
4. Updated the dlopen code in the test to use the SkOSLibrary ports
   instead of calling dlopen/dlclose directly.
5. Fix bugs in SkCanvasStateUtils that were uncovered as part of
   always running these tests.
6. Switches the define away from SK_SUPPORT_LEGACY_CLIPTOLAYERFLAG
   to SK_TEST_CANVAS_STATE_CROSS_LIBRARY, since these tests are not
   strictly speaking testing the unclipped layer feature.

Change-Id: Id4ae41e4bc3fb8227367deac899878ecf9d7b98e
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/341003
Reviewed-by: Derek Sollenberger <djsollen@google.com>
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
This commit is contained in:
Michael Ludwig 2020-12-04 12:58:35 -05:00 committed by Skia Commit-Bot
parent ae95077db6
commit 3021795041
9 changed files with 99 additions and 52 deletions

View File

@ -2034,6 +2034,15 @@ if (skia_enable_tools) {
"modules/svg",
]
}
# optional separate library to dlopen when running CanvasStateTests.
shared_library("canvas_state_lib") {
sources = [
"tests/CanvasStateHelpers.cpp",
"tests/CanvasStateHelpers.h",
]
deps = [ ":skia" ]
}
}
if (!is_win) {

View File

@ -2999,7 +2999,7 @@ const SkPaint& SkCanvas::LayerIter::paint() const {
}
SkIRect SkCanvas::LayerIter::clipBounds() const {
return fImpl->fDevice->getGlobalBounds();
return fImpl->fDevice->devClipBounds();
}
int SkCanvas::LayerIter::x() const { return fDeviceOrigin.fX; }

View File

@ -10,5 +10,6 @@
void* SkLoadDynamicLibrary(const char* libraryName);
void* SkGetProcedureAddress(void* library, const char* functionName);
bool SkFreeDynamicLibrary(void* library);
#endif

View File

@ -18,4 +18,9 @@ void* SkLoadDynamicLibrary(const char* libraryName) {
void* SkGetProcedureAddress(void* library, const char* functionName) {
return dlsym(library, functionName);
}
bool SkFreeDynamicLibrary(void* library) {
return dlclose(library) == 0;
}
#endif//!defined(SK_BUILD_FOR_WIN)

View File

@ -18,4 +18,8 @@ void* SkGetProcedureAddress(void* library, const char* functionName) {
return reinterpret_cast<void*>(::GetProcAddress((HMODULE)library, functionName));
}
bool SkFreeDynamicLibrary(void* library) {
return FreeLibrary((HMODULE)library);
}
#endif//defined(SK_BUILD_FOR_WIN)

View File

@ -110,12 +110,20 @@ public:
}
~SkCanvasState_v1() {
// loop through the layers and free the data allocated to the clipRects
// loop through the layers and free the data allocated to the clipRects.
// See setup_MC_state, clipRects is only allocated when the clip isn't empty; and empty
// is implicitly represented as clipRectCount == 0.
for (int i = 0; i < layerCount; ++i) {
sk_free(layers[i].mcState.clipRects);
if (layers[i].mcState.clipRectCount > 0) {
sk_free(layers[i].mcState.clipRects);
}
}
sk_free(mcState.clipRects);
if (mcState.clipRectCount > 0) {
sk_free(mcState.clipRects);
}
// layers is always allocated, even if it's with sk_malloc(0), so this is safe.
sk_free(layers);
}

View File

@ -6,7 +6,11 @@
*/
#include "include/core/SkTypes.h"
#ifdef SK_SUPPORT_LEGACY_CLIPTOLAYERFLAG
#if !defined(SK_TEST_CANVAS_STATE_CROSS_LIBRARY)
#include "tests/CanvasStateHelpers.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkClipOp.h"
#include "include/core/SkColor.h"
@ -15,7 +19,6 @@
#include "include/core/SkRegion.h"
#include "include/core/SkScalar.h"
#include "include/utils/SkCanvasStateUtils.h"
#include "tests/CanvasStateHelpers.h"
#include <memory>
@ -72,4 +75,5 @@ extern "C" bool complex_clips_draw_from_canvas_state(SkCanvasState* state,
complex_clips_draw(canvas.get(), left, top, right, bottom, clipOp, localRegion);
return true;
}
#endif // SK_SUPPORT_LEGACY_CLIPTOLAYERFLAG
#endif // SK_TEST_CANVAS_STATE_CROSS_LIBRARY

View File

@ -10,11 +10,22 @@
#include "include/core/SkTypes.h"
#ifdef SK_SUPPORT_LEGACY_CLIPTOLAYERFLAG
// See CanvasStateTest. These functions are either linked in to 'dm' directly (when this flag is
// not defined), or built in a shared library that is dlopened by the test. In that case, they
// should not be visible in 'dm', but the shared library will not have this flag set and compiles
// them as expected.
#if !defined(SK_TEST_CANVAS_STATE_CROSS_LIBRARY)
class SkCanvas;
class SkCanvasState;
class SkRegion;
#if defined(SK_BUILD_FOR_WIN)
#define EXPORT _declspec(dllexport)
#else
#define EXPORT
#endif
/*
* Helper function to perform drawing to an SkCanvas. Used by both
* test_complex_layers and complex_layers_draw_from_canvas_state.
@ -28,7 +39,7 @@ void complex_layers_draw(SkCanvas* canvas, float left, float top,
* Used by test_complex_layers test in CanvasStateTest. Marked as extern
* so it can be called from a separate library.
*/
extern "C" bool complex_layers_draw_from_canvas_state(SkCanvasState* state,
extern "C" bool EXPORT complex_layers_draw_from_canvas_state(SkCanvasState* state,
float left, float top, float right, float bottom, int32_t spacer);
/*
@ -44,9 +55,9 @@ void complex_clips_draw(SkCanvas* canvas, int32_t left, int32_t top,
* Used by test_complex_clips test in CanvasStateTest. Marked as extern
* so it can be called from a separate library.
*/
extern "C" bool complex_clips_draw_from_canvas_state(SkCanvasState* state,
extern "C" bool EXPORT complex_clips_draw_from_canvas_state(SkCanvasState* state,
int32_t left, int32_t top, int32_t right, int32_t bottom, int32_t clipOp,
int32_t regionRects, int32_t* rectCoords);
#endif // SK_SUPPORT_LEGACY_CLIPTOLAYERFLAG
#endif // SK_TEST_CANVAS_STATE_CROSS_LIBRARY
#endif // CanvasStateHelpers_DEFINED

View File

@ -21,7 +21,6 @@
#include "src/core/SkCanvasPriv.h"
#include "src/core/SkClipOpPriv.h"
#include "src/core/SkTLazy.h"
#include "tests/CanvasStateHelpers.h"
#include "tests/Test.h"
#include "tools/flags/CommandLineFlags.h"
@ -29,48 +28,59 @@
class SkCanvasState;
// dlopen and the library flag are only used for tests which require this flag.
#ifdef SK_SUPPORT_LEGACY_CLIPTOLAYERFLAG
#include <dlfcn.h>
// Uncomment to include tests of CanvasState across a library boundary. This will change how 'dm'
// is built so that the functions defined in CanvasStateHelpers do not exist inside 'dm', and are
// instead compiled as part of the 'canvas_state_lib' build target. This produces a shared library
// that must be passed to 'dm' using the --library flag when running.
// #define SK_TEST_CANVAS_STATE_CROSS_LIBRARY
// Must be included after SK_TEST_CANVAS_STATE_CROSS_LIBRARY is defined
#include "tests/CanvasStateHelpers.h"
// dlopen, the library flag and canvas state helpers are only used for tests which require this flag
#if defined(SK_TEST_CANVAS_STATE_CROSS_LIBRARY)
static DEFINE_string(library, "",
"Support library to use for CanvasState test. If empty (the default), "
"the test will be run without crossing a library boundary. Otherwise, "
"it is expected to be a full path to a shared library file, which will"
" be dynamically loaded. Functions from the library will be called to "
"test SkCanvasState. Instructions for generating the library are in "
"gyp/canvas_state_lib.gyp");
"Support library to use for CanvasState test. Must be provided when"
" SK_TEST_CANVAS_STATE_CROSS_LIBRARY to specify the dynamically loaded library"
" that receives the captured canvas state. Functions from the library will be"
" called to test SkCanvasState. The library is built from the canvas_state_lib"
" target");
#include "src/ports/SkOSLibrary.h"
// This class calls dlopen on the library passed in to the command line flag library, and handles
// calling dlclose when it goes out of scope.
// Automatically loads library passed to --library flag and closes it when it goes out of scope.
class OpenLibResult {
public:
// If the flag library was passed to this run of the test, attempt to open it using dlopen and
// report whether it succeeded.
OpenLibResult(skiatest::Reporter* reporter) {
if (FLAGS_library.count() == 1) {
fHandle = dlopen(FLAGS_library[0], RTLD_LAZY | RTLD_LOCAL);
REPORTER_ASSERT(reporter, fHandle != nullptr, "Failed to open library!");
fLibrary = SkLoadDynamicLibrary(FLAGS_library[0]);
REPORTER_ASSERT(reporter, fLibrary != nullptr, "Failed to open library!");
} else {
fHandle = nullptr;
fLibrary = nullptr;
}
}
// Automatically call dlclose when going out of scope.
~OpenLibResult() {
if (fHandle) {
dlclose(fHandle);
if (fLibrary) {
SkFreeDynamicLibrary(fLibrary);
}
}
// Pointer to the shared library object.
void* handle() { return fHandle; }
// Load a function address from the library object, or null if the library had failed
void* procAddress(const char* funcName) {
if (fLibrary) {
return SkGetProcedureAddress(fLibrary, funcName);
}
return nullptr;
}
private:
void* fHandle;
void* fLibrary;
};
#endif
static void write_image(const SkImage* img, const char path[]) {
auto data = img->encodeToData();
SkFILEWStream(path).write(data->data(), data->size());
@ -96,7 +106,7 @@ static void compare(skiatest::Reporter* reporter, SkImage* img0, SkImage* img1)
REPORTER_ASSERT(reporter, pm[0].computeByteSize() == pm[1].computeByteSize());
REPORTER_ASSERT(reporter, pm[0].rowBytes() == (size_t)pm[0].width() * pm[0].info().bytesPerPixel());
REPORTER_ASSERT(reporter, pm[1].rowBytes() == (size_t)pm[1].width() * pm[1].info().bytesPerPixel());
if (memcmp(pm[0].addr(0, 0), pm[1].addr(0, 0), pm[0].computeByteSize())) {
if (memcmp(pm[0].addr(0, 0), pm[1].addr(0, 0), pm[0].computeByteSize()) != 0) {
REPORTER_ASSERT(reporter, false);
}
}
@ -125,13 +135,12 @@ DEF_TEST(CanvasState_test_complex_layers, reporter) {
bool (*drawFn)(SkCanvasState* state, float l, float t,
float r, float b, int32_t s);
#if defined(SK_TEST_CANVAS_STATE_CROSS_LIBRARY)
OpenLibResult openLibResult(reporter);
if (openLibResult.handle() != nullptr) {
*(void**) (&drawFn) = dlsym(openLibResult.handle(),
"complex_layers_draw_from_canvas_state");
} else {
drawFn = complex_layers_draw_from_canvas_state;
}
*(void**) (&drawFn) = openLibResult.procAddress("complex_layers_draw_from_canvas_state");
#else
drawFn = complex_layers_draw_from_canvas_state;
#endif
REPORTER_ASSERT(reporter, drawFn);
if (!drawFn) {
@ -186,11 +195,9 @@ DEF_TEST(CanvasState_test_complex_layers, reporter) {
compare(reporter, images[0].get(), images[1].get());
}
}
#endif
////////////////////////////////////////////////////////////////////////////////
#ifdef SK_SUPPORT_LEGACY_CLIPTOLAYERFLAG
DEF_TEST(CanvasState_test_complex_clips, reporter) {
const int WIDTH = 400;
const int HEIGHT = 400;
@ -216,7 +223,7 @@ DEF_TEST(CanvasState_test_complex_clips, reporter) {
const SkRegion::Op clipOps[] = { SkRegion::kIntersect_Op,
SkRegion::kIntersect_Op,
SkRegion::kReplace_Op,
SkRegion::kDifference_Op,
};
const SkCanvas::SaveLayerFlags flags[] = {
static_cast<SkCanvas::SaveLayerFlags>(SkCanvasPriv::kDontClipToLayer_SaveLayerFlag),
@ -229,13 +236,12 @@ DEF_TEST(CanvasState_test_complex_clips, reporter) {
int32_t r, int32_t b, int32_t clipOp,
int32_t regionRects, int32_t* rectCoords);
#if defined(SK_TEST_CANVAS_STATE_CROSS_LIBRARY)
OpenLibResult openLibResult(reporter);
if (openLibResult.handle() != nullptr) {
*(void**) (&drawFn) = dlsym(openLibResult.handle(),
"complex_clips_draw_from_canvas_state");
} else {
drawFn = complex_clips_draw_from_canvas_state;
}
*(void**) (&drawFn) = openLibResult.procAddress("complex_clips_draw_from_canvas_state");
#else
drawFn = complex_clips_draw_from_canvas_state;
#endif
REPORTER_ASSERT(reporter, drawFn);
if (!drawFn) {
@ -253,7 +259,7 @@ DEF_TEST(CanvasState_test_complex_clips, reporter) {
SkPaint paint;
paint.setAlpha(128);
for (size_t j = 0; j < SK_ARRAY_COUNT(flags); ++j) {
for (size_t j = 0; j < SK_ARRAY_COUNT(clipOps); ++j) {
SkRect layerBounds = SkRect::Make(layerRect);
canvas->saveLayer(SkCanvas::SaveLayerRec(&layerBounds, &paint, flags[j]));
@ -293,7 +299,6 @@ DEF_TEST(CanvasState_test_complex_clips, reporter) {
compare(reporter, images[0].get(), images[1].get());
}
#endif
////////////////////////////////////////////////////////////////////////////////