build
build.log
tct*core.h
+rules.mk
\ No newline at end of file
#include "image-loaders.h"
#include <dali-test-suite-utils.h>
+
+class StubImageLoaderClient : public Dali::SlpPlatform::ResourceLoadingClient
+{
+public:
+ StubImageLoaderClient() {}
+ ~StubImageLoaderClient() {}
+
+ virtual void InterruptionPoint() const {}
+};
+
AutoCloseFile::AutoCloseFile( FILE *fp )
: filePtr( fp )
{
// Load Bitmap and check its return values.
- DALI_TEST_CHECK( functions.loader( fp, *bitmap, attributes ) );
+ DALI_TEST_CHECK( functions.loader( fp, *bitmap, attributes, StubImageLoaderClient() ) );
DALI_TEST_EQUALS( image.width, attributes.GetWidth(), TEST_LOCATION );
DALI_TEST_EQUALS( image.height, attributes.GetHeight(), TEST_LOCATION );
Dali::Integration::BitmapPtr bitmapPtr( bitmap );
Dali::ImageAttributes attributes;
- DALI_TEST_CHECK( functions.loader( fp, *bitmap, attributes ) );
+ DALI_TEST_CHECK( functions.loader( fp, *bitmap, attributes, StubImageLoaderClient() ) );
Dali::PixelBuffer* bufferPtr( bitmapPtr->GetBuffer() );
#include <dali/dali.h>
#include <dali/integration-api/bitmap.h>
+#include "platform-abstractions/slp/resource-loader/resource-loading-client.h"
// Simple structure to close the file when finished with it.
struct AutoCloseFile
*/
struct LoadFunctions
{
- typedef bool (*LoadBitmapFunction)(FILE*, Dali::Integration::Bitmap&, Dali::ImageAttributes&);
+ typedef bool (*LoadBitmapFunction)(FILE*, Dali::Integration::Bitmap&, Dali::ImageAttributes&, const Dali::SlpPlatform::ResourceLoadingClient& client);
typedef bool (*LoadBitmapHeaderFunction)(FILE*, const Dali::ImageAttributes& attrs, unsigned int& width, unsigned int& height );
LoadFunctions( LoadBitmapHeaderFunction _header, LoadBitmapFunction _loader );
*
*/
+#include <unistd.h>
#include <iostream>
#include <stdlib.h>
#include <dali/dali.h>
* A value of 1000 is enough to make load tests take tens of seconds each
* on desktop. */
const unsigned NUM_LOAD_GROUPS_TO_ISSUE = 200;
+
+/**
+ * The number of loads to issue when they will be cancelled.
+ * Cancelled loads are cheap so we do a lot.
+ */
+const unsigned NUM_CANCELLED_LOAD_GROUPS_TO_ISSUE = NUM_LOAD_GROUPS_TO_ISSUE * 10;
+
/** The number of times to ask for resource load status. */
const unsigned MAX_NUM_RESOURCE_TRIES = 5;
TEST_IMAGE_DIR "/interlaced.gif",
TEST_IMAGE_DIR "/pattern.gif"
};
-const unsigned NUM_VALID_IMAGES = 5u;
-
+const unsigned NUM_VALID_IMAGES = sizeof(VALID_IMAGES) / sizeof(VALID_IMAGES[0]);
///@ToDo: Add valid ktx, ico, and wbmp image examples.
/** Live platform abstraction recreated for each test case. */
Integration::PlatformAbstraction * gAbstraction = 0;
+/** A variety of ImageAttributes to reach different code paths that have embedded code paths. */
+std::vector<ImageAttributes> gCancelAttributes;
+
} // anon namespace
void utc_dali_loading_startup(void)
{
test_return_value = TET_UNDEF;
gAbstraction = CreatePlatformAbstraction();
+
+ // Setup some ImageAttributes to engage post-processing stages:
+
+ ImageAttributes scaleToFillAttributes;
+ scaleToFillAttributes.SetScalingMode( ImageAttributes::ScaleToFill );
+ scaleToFillAttributes.SetSize( 160, 120 );
+ gCancelAttributes.push_back( scaleToFillAttributes );
+
+ // Hit the derived dimensions code:
+ ImageAttributes scaleToFillAttributesDeriveWidth = scaleToFillAttributes;
+ scaleToFillAttributesDeriveWidth.SetSize( 0, 120 );
+ gCancelAttributes.push_back( scaleToFillAttributesDeriveWidth );
+
+ ImageAttributes scaleToFillAttributesDeriveHeight = scaleToFillAttributes;
+ scaleToFillAttributesDeriveHeight.SetSize( 160, 0 );
+ gCancelAttributes.push_back( scaleToFillAttributesDeriveHeight );
+
+ // Try to push a tall crop:
+ ImageAttributes scaleToFillAttributesTall = scaleToFillAttributes;
+ scaleToFillAttributesTall.SetSize( 160, 480 );
+ ImageAttributes scaleToFillAttributesTall2 = scaleToFillAttributes;
+ scaleToFillAttributesTall2.SetSize( 160, 509 );
+ ImageAttributes scaleToFillAttributesTall3 = scaleToFillAttributes;
+ scaleToFillAttributesTall3.SetSize( 37, 251 );
+ gCancelAttributes.push_back( scaleToFillAttributesTall );
+ gCancelAttributes.push_back( scaleToFillAttributesTall2 );
+ gCancelAttributes.push_back( scaleToFillAttributesTall3 );
+
+ // Try to push a wide crop:
+ ImageAttributes scaleToFillAttributesWide = scaleToFillAttributes;
+ scaleToFillAttributesWide.SetSize( 320, 60 );
+ ImageAttributes scaleToFillAttributesWide2 = scaleToFillAttributes;
+ scaleToFillAttributesWide2.SetSize( 317, 60 );
+ ImageAttributes scaleToFillAttributesWide3 = scaleToFillAttributes;
+ scaleToFillAttributesWide3.SetSize( 317, 53 );
+ gCancelAttributes.push_back( scaleToFillAttributesWide );
+ gCancelAttributes.push_back( scaleToFillAttributesWide2 );
+ gCancelAttributes.push_back( scaleToFillAttributesWide3 );
+
+ ImageAttributes shrinkToFitAttributes = scaleToFillAttributes;
+ shrinkToFitAttributes.SetScalingMode( ImageAttributes::ShrinkToFit );
+ gCancelAttributes.push_back( shrinkToFitAttributes );
+
+ ImageAttributes fitWidthAttributes = scaleToFillAttributes;
+ fitWidthAttributes.SetScalingMode( ImageAttributes::FitWidth );
+ gCancelAttributes.push_back( fitWidthAttributes );
+
+ ImageAttributes fitHeightAttributes = scaleToFillAttributes;
+ fitHeightAttributes.SetScalingMode( ImageAttributes::FitHeight );
+ gCancelAttributes.push_back( fitHeightAttributes );
+
+ ///@ToDo: Add attribute variants for all scale modes.
+
+ // Pad the array to a prime number to mitigate any accidental periodic
+ // patterns in which image file has which attributes applied to its load:
+ srand48( 104729 );
+ const float lastUniques = gCancelAttributes.size() - 0.001f;
+ while( gCancelAttributes.size() < 61u )
+ {
+ gCancelAttributes.push_back( gCancelAttributes[unsigned(drand48() * lastUniques)] );
+ }
}
void utc_dali_loading_cleanup(void)
for( unsigned i = 0; i < MAX_NUM_RESOURCE_TRIES && resourceSink.mGrandTotalCompletions < loadsLaunched; ++i )
{
- sleep( 1 );
+ tet_printf( "Draining sleep %u, at total completion count %u of %u.\n", i, resourceSink.mGrandTotalCompletions, loadsLaunched );
+ usleep( 1200 * 1000 );
gAbstraction->GetResources( resourceSink );
}
DALI_TEST_CHECK( 0 == resourceSink.mFailureCounts.size() );
// Check that each success was reported exactly once:
- for(ResourceCounterMap::const_iterator it = resourceSink.mSuccessCounts.begin(), end = resourceSink.mSuccessCounts.end(); it != end; ++it )
+ for( ResourceCounterMap::const_iterator it = resourceSink.mSuccessCounts.begin(), end = resourceSink.mSuccessCounts.end(); it != end; ++it )
{
DALI_TEST_CHECK( it->second == 1u );
}
END_TEST;
}
+/**
+ * @brief Test case for load cancellation.
+ *
+ * Load lots of images in batches, cancelling all in a batch after a small delay to
+ * allow the first of a batch to be launched before cancellation starts.
+ * Assert that all loads issued are either completed or cancelled.
+ */
+int UtcDaliCancelAllLoads(void)
+{
+ tet_printf( "Running load cancel-all test.\n" );
+
+ DALI_ASSERT_ALWAYS( gAbstraction != 0 );
+
+ // Start a bunch of loads that should work:
+
+ Dali::Integration::LoadResourcePriority priority = LoadPriorityNormal;
+ unsigned loadsLaunched = 0;
+
+ for( unsigned loadGroup = 0; loadGroup < NUM_CANCELLED_LOAD_GROUPS_TO_ISSUE; ++loadGroup )
+ {
+ // Issue load requests for a batch of images:
+ for( unsigned validImage = 0; validImage < NUM_VALID_IMAGES; ++validImage )
+ {
+ const BitmapResourceType bitmapResourceType( gCancelAttributes[ loadsLaunched % gCancelAttributes.size() ] );
+ const ResourceId resourceId = loadGroup * NUM_VALID_IMAGES + validImage + 1;
+ gAbstraction->LoadResource( ResourceRequest( resourceId, bitmapResourceType, VALID_IMAGES[validImage], priority ) );
+ loadsLaunched += 1;
+ }
+
+ // Let the first image in the batch start to load:
+ usleep( 5000 ); // This number is tuned. Turn it up too much and all loads will complete and the test will take so long it seems to hang.
+
+ // Cancel all the launched loads from oldest to newest:
+ for( unsigned validImage = 0; validImage < NUM_VALID_IMAGES; ++validImage )
+ {
+ const ResourceId resourceId = loadGroup * NUM_VALID_IMAGES + validImage + 1;
+ gAbstraction->CancelLoad( resourceId, ResourceBitmap );
+ }
+ }
+
+ // Drain the completed loads:
+ Dali::Internal::Platform::ResourceCollector resourceSink;
+
+ unsigned lastCompletions = -1;
+ for( unsigned i = 0; i < MAX_NUM_RESOURCE_TRIES && resourceSink.mGrandTotalCompletions < loadsLaunched && resourceSink.mGrandTotalCompletions != lastCompletions; ++i )
+ {
+ lastCompletions = resourceSink.mGrandTotalCompletions;
+ gAbstraction->GetResources( resourceSink );
+ tet_printf( "Draining sleep %u, at total completion count %u of %u.\n", i, resourceSink.mGrandTotalCompletions, loadsLaunched );
+ usleep( 100 * 1000 );
+ }
+
+ // Check the loads completed as expected:
+
+ tet_printf( "Issued Loads: %u, Completed Loads: %u, Successful Loads: %u, Failed Loads: %u \n", loadsLaunched, resourceSink.mGrandTotalCompletions, unsigned(resourceSink.mSuccessCounts.size()), unsigned(resourceSink.mFailureCounts.size()) );
+ DALI_TEST_CHECK( loadsLaunched > resourceSink.mGrandTotalCompletions );
+ DALI_TEST_CHECK( loadsLaunched > resourceSink.mSuccessCounts.size() );
+ DALI_TEST_CHECK( 0 == resourceSink.mFailureCounts.size() );
+
+ // Check that each success was reported exactly once:
+ for( ResourceCounterMap::const_iterator it = resourceSink.mSuccessCounts.begin(), end = resourceSink.mSuccessCounts.end(); it != end; ++it )
+ {
+ DALI_TEST_CHECK( it->second == 1u );
+ }
+
+ END_TEST;
+}
+
+/**
+ * @brief Test case for load cancellation.
+ *
+ * Load lots, cancel a subset and be sure the wrong loads are never cancelled
+ * and that all loads issued are either completed or cancelled.
+ */
+int UtcDaliCancelSomeLoads(void)
+{
+ tet_printf( "Running load cancel load subset test.\n" );
+
+ DALI_ASSERT_ALWAYS( gAbstraction != 0 );
+
+ // Start a bunch of loads that should work:
+
+ Dali::Integration::LoadResourcePriority priority = LoadPriorityNormal;
+ unsigned loadsLaunched = 0;
+
+ std::set<Integration::ResourceId> cancelledLoadSet;
+
+ for( unsigned loadGroup = 0; loadGroup < NUM_LOAD_GROUPS_TO_ISSUE; ++loadGroup )
+ {
+ // Issue load requests for a batch of images:
+ for( unsigned validImage = 0; validImage < NUM_VALID_IMAGES; ++validImage )
+ {
+ const BitmapResourceType bitmapResourceType( gCancelAttributes[ loadsLaunched % gCancelAttributes.size() ] );
+ const ResourceId resourceId = loadGroup * NUM_VALID_IMAGES + validImage + 1;
+ gAbstraction->LoadResource( ResourceRequest( resourceId, bitmapResourceType, VALID_IMAGES[validImage], priority ) );
+ loadsLaunched += 1;
+ }
+
+ // Let the first image in the batch start to load so we can try to cancel it in-flight:
+ usleep( 17000 );
+ ///@Note: The log should show cancellations of many in-flight loads in desktop builds with info-level logging enabled (e.g., "INFO: DALI: : CheckForCancellation: Cancelled in-flight resource (21)."). If it doesn't, the above delay may need to be adjusted.
+
+ // Cancel just two loads (hopefully one in-flight and one queued):
+
+ // Cancel first load, hopefully while it is in-flight:
+ const ResourceId cancelledInFlight = loadGroup * NUM_VALID_IMAGES + 1;
+ gAbstraction->CancelLoad( cancelledInFlight, ResourceBitmap );
+ cancelledLoadSet.insert( cancelledInFlight );
+
+ // Cancel second load, that is still queued:
+ const ResourceId cancelledFromQueue = loadGroup * NUM_VALID_IMAGES + NUM_VALID_IMAGES;
+ gAbstraction->CancelLoad( cancelledFromQueue, ResourceBitmap );
+ cancelledLoadSet.insert( cancelledFromQueue );
+ }
+
+ // Drain the completed loads:
+
+ Dali::Internal::Platform::ResourceCollector resourceSink;
+
+ unsigned lastCompletions = -1;
+ for( unsigned i = 0; i < MAX_NUM_RESOURCE_TRIES && resourceSink.mGrandTotalCompletions < loadsLaunched && resourceSink.mGrandTotalCompletions != lastCompletions; ++i )
+ {
+ lastCompletions = resourceSink.mGrandTotalCompletions;
+ gAbstraction->GetResources( resourceSink );
+ tet_printf( "Draining sleep %u, at total completion count %u of %u.\n", i, resourceSink.mGrandTotalCompletions, loadsLaunched );
+ usleep( 100 * 1000 );
+ }
+
+ // Check the loads completed as expected:
+
+ tet_printf( "Issued Loads: %u, Completed Loads: %u, Successful Loads: %u, Failed Loads: %u \n", loadsLaunched, resourceSink.mGrandTotalCompletions, unsigned(resourceSink.mSuccessCounts.size()), unsigned(resourceSink.mFailureCounts.size()) );
+ DALI_TEST_CHECK( loadsLaunched >= resourceSink.mGrandTotalCompletions );
+ DALI_TEST_CHECK( loadsLaunched >= resourceSink.mSuccessCounts.size() );
+ DALI_TEST_CHECK( 0 == resourceSink.mFailureCounts.size() );
+
+ // Check that if an image was not loaded, it is one of the ones that was cancelled:
+ // This is the main point of this test case.
+ for( unsigned resourceId = 1; resourceId <= NUM_LOAD_GROUPS_TO_ISSUE * NUM_VALID_IMAGES; ++resourceId )
+ {
+ if( resourceSink.mCompletionStatuses.find( resourceId ) == resourceSink.mCompletionStatuses.end() )
+ {
+ DALI_TEST_CHECK( cancelledLoadSet.find( resourceId ) != cancelledLoadSet.end() );
+ }
+ }
+
+ // Check that each success was reported exactly once:
+ for(ResourceCounterMap::const_iterator it = resourceSink.mSuccessCounts.begin(), end = resourceSink.mSuccessCounts.end(); it != end; ++it )
+ {
+ DALI_TEST_CHECK( it->second == 1u );
+ }
+
+ END_TEST;
+}
--- /dev/null
+#ifndef _DALI_INTERNAL_PLATFORM_ATOMICS_H_
+#define _DALI_INTERNAL_PLATFORM_ATOMICS_H_
+
+/*
+ * Copyright (c) 2014 Samsung Electronics Co., Ltd.
+ *
+ * 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.
+ *
+ */
+
+/*
+ * atomics.h
+ *
+ * Interface for atomic memory operations.
+ * There may be platform-specific versions of this file in other directories.
+ */
+
+namespace Dali
+{
+namespace Internal
+{
+
+/**
+ * @brief Atomic write to an aligned memory location in cacheable memory.
+ *
+ * For common platforms with coherent caches such as ARM mpcore and Intel CPUs,
+ * a cacheline can be in a writeable state in the L1 cache of exactly one core
+ * at a time. Therefore, a write to a location that does not require a read /
+ * modify / write cycle or cross a cacheline boundary is automatically
+ * atomic.
+ *
+ * @param address A pointer to a location in a cacheable memory region that is
+ * aligned to a 4 byte boundary.
+ * @param value A value to assign to the memory location in address.
+ */
+inline void AtomicWriteToCacheableAlignedAddress( volatile uint32_t * const address, const uint32_t value )
+{
+ DALI_ASSERT_DEBUG( ptrdiff_t(address) % 4 == 0 && "Address must be 32 bit aligned" );
+ *address = value;
+}
+
+/**
+ * @brief Atomic read from an aligned memory location in cacheable memory.
+ *
+ * For common platforms with coherent caches such as ARM mpcore and Intel CPUs,
+ * a cacheline can be in a writeable state in the L1 cache of exactly one core
+ * at a time. Therefore, a read a location that does not cross a cacheline
+ * boundary is automatically atomic.
+ *
+ * @param address A pointer to a location in a cacheable memory region that is
+ * aligned to a 4 byte boundary.
+ * @return The value stored at the memory location in address.
+ */
+inline uint32_t AtomicReadFromCacheableAlignedAddress( const volatile uint32_t * const address )
+{
+ DALI_ASSERT_DEBUG( ptrdiff_t(address) % 4 == 0 && "Address must be 32 bit aligned" );
+ return *address;
+}
+
+} /* namespace Internal */
+} /* namespace Dali */
+
+#endif /* _DALI_INTERNAL_PLATFORM_ATOMICS_H_ */
return ret;
}
-bool LoadBitmapFromBmp(FILE *fp, Bitmap& bitmap, ImageAttributes& attributes)
+bool LoadBitmapFromBmp( FILE *fp, Bitmap& bitmap, ImageAttributes& attributes, const ResourceLoadingClient& client )
{
DALI_ASSERT_DEBUG(bitmap.GetPackedPixelsProfile() != 0 && "Need a packed pixel bitmap to load into.");
if(fp == NULL)
namespace Integration
{
- class Bitmap;
+class Bitmap;
}
struct ImageAttributes;
namespace SlpPlatform
{
+class ResourceLoadingClient;
+
namespace Bmp
{
const unsigned char MAGIC_BYTE_1 = 0x42;
* @param[in] attributes Describes the dimensions, pixel format and other details for loading the image data
* @return true if file decoded successfully, false otherwise
*/
-bool LoadBitmapFromBmp(FILE *fp, Integration::Bitmap& bitmap, ImageAttributes& attributes);
+bool LoadBitmapFromBmp( FILE *fp, Integration::Bitmap& bitmap, ImageAttributes& attributes, const ResourceLoadingClient& client );
/**
* Loads the header of a BMP file and fills in the width and height appropriately.
return LoadGifHeader(fp, width, height, &gifInfo);
}
-bool LoadBitmapFromGif(FILE *fp, Bitmap& bitmap, ImageAttributes& attributes)
+bool LoadBitmapFromGif(FILE *fp, Bitmap& bitmap, ImageAttributes& attributes, const ResourceLoadingClient& client )
{
// Load the GIF Header file.
namespace SlpPlatform
{
+class ResourceLoadingClient;
+
namespace Gif
{
const unsigned char MAGIC_BYTE_1 = 0x47;
* @param[in] attributes Describes the dimensions, pixel format and other details for loading the image data
* @return true if file decoded successfully, false otherwise
*/
-bool LoadBitmapFromGif(FILE *fp, Integration::Bitmap& bitmap, ImageAttributes& attributes);
+bool LoadBitmapFromGif( FILE *fp, Integration::Bitmap& bitmap, ImageAttributes& attributes, const ResourceLoadingClient& client );
/**
* Loads the header of a GIF file and fills in the width and height appropriately.
return true;
}
-bool LoadBitmapFromIco(FILE *fp, Integration::Bitmap& bitmap, ImageAttributes& attributes)
+bool LoadBitmapFromIco( FILE *fp, Integration::Bitmap& bitmap, ImageAttributes& attributes, const ResourceLoadingClient& client )
{
IcoData chosen;
Dali::Vector<unsigned char> map;
namespace SlpPlatform
{
+
+class ResourceLoadingClient;
+
namespace Ico
{
//00 00 01 00 01 00 20 20
const unsigned char MAGIC_BYTE_2 = 0x00;
}
-bool LoadBitmapFromIco(FILE *fp, Integration::Bitmap& bitmap, ImageAttributes& attributes);
+bool LoadBitmapFromIco( FILE *fp, Integration::Bitmap& bitmap, ImageAttributes& attributes, const ResourceLoadingClient& client );
bool LoadIcoHeader(FILE *fp, const ImageAttributes& attributes, unsigned int &width, unsigned int &height );
*
*/
+// INTERNAL HEADERS
#include "loader-jpeg.h"
-#include <turbojpeg.h>
-#include <jpeglib.h>
-#include <cstring>
-
-#include <dali/integration-api/debug.h>
+#include "resource-loading-client.h"
#include <dali/integration-api/bitmap.h>
#include <dali/public-api/images/image-attributes.h>
#include <resource-loader/debug/resource-loader-debug.h>
#include "platform-capabilities.h"
+
+// EXTERNAL HEADERS
#include <libexif/exif-data.h>
#include <libexif/exif-loader.h>
#include <libexif/exif-tag.h>
+#include <turbojpeg.h>
+#include <jpeglib.h>
+#include <cstring>
#include <setjmp.h>
-#include <boost/thread.hpp>
+
namespace Dali
{
return true;
}
-bool LoadBitmapFromJpeg( FILE *fp, Bitmap& bitmap, ImageAttributes& attributes )
+bool LoadBitmapFromJpeg( FILE *fp, Bitmap& bitmap, ImageAttributes& attributes, const ResourceLoadingClient& client )
{
int flags=(FORCEMMX ? TJ_FORCEMMX : 0) |
(FORCESSE ? TJ_FORCESSE : 0) |
}
// Allow early cancellation between the load and the decompress:
- boost::this_thread::interruption_point(); ///@warning This can throw an exception.
+ client.InterruptionPoint();
AutoJpg autoJpg(tjInitDecompress());
unsigned char * const bitmapPixelBuffer = bitmap.GetPackedPixelsProfile()->ReserveBuffer(Pixel::RGB888, scaledPostXformWidth, scaledPostXformHeight);
// Allow early cancellation before decoding:
- boost::this_thread::interruption_point(); ///@warning This can throw an exception.
+ client.InterruptionPoint();
const int pitch = scaledPreXformWidth * DECODED_PIXEL_SIZE;
if( tjDecompress2( autoJpg.GetHandle(), jpegBufferPtr, jpegBufferSize, bitmapPixelBuffer, scaledPreXformWidth, pitch, scaledPreXformHeight, DECODED_PIXEL_LIBJPEG_TYPE, flags ) == -1 )
if( transform != JPGFORM_NONE )
{
// Allow early cancellation before shuffling pixels around on the CPU:
- boost::this_thread::interruption_point(); ///@warning This can throw an exception.
+ client.InterruptionPoint();
}
bool result = false;
namespace SlpPlatform
{
+class ResourceLoadingClient;
+
namespace Jpeg
{
const unsigned char MAGIC_BYTE_1 = 0xFF;
* @param[in] attributes Describes the dimensions, pixel format and other details for loading the image data
* @return true if file decoded successfully, false otherwise
*/
-bool LoadBitmapFromJpeg(FILE *fp, Integration::Bitmap& bitmap, ImageAttributes& attributes);
+bool LoadBitmapFromJpeg( FILE *fp, Integration::Bitmap& bitmap, ImageAttributes& attributes, const ResourceLoadingClient& client );
/**
* Loads the header of a JPEG file and fills in the width and height appropriately.
}
// File loading API entry-point:
-bool LoadBitmapFromKtx(FILE * const fp, Bitmap& bitmap, ImageAttributes& attributes)
+bool LoadBitmapFromKtx( FILE * const fp, Bitmap& bitmap, ImageAttributes& attributes, const ResourceLoadingClient& client )
{
DALI_COMPILE_TIME_ASSERT( sizeof(Byte) == 1);
DALI_COMPILE_TIME_ASSERT( sizeof(uint32_t) == 4);
namespace SlpPlatform
{
+class ResourceLoadingClient;
+
namespace Ktx
{
const unsigned char MAGIC_BYTE_1 = 0xAB;
* @param[in] attributes Describes the dimensions, pixel format and other details for loading the image data
* @return true if file loaded successfully, false otherwise
*/
-bool LoadBitmapFromKtx(FILE * const fp, Integration::Bitmap& bitmap, ImageAttributes& attributes);
+bool LoadBitmapFromKtx( FILE * const fp, Integration::Bitmap& bitmap, ImageAttributes& attributes, const ResourceLoadingClient& client );
/**
* Loads the header of a KTX file and fills in the width and height appropriately.
return success;
}
-bool LoadBitmapFromPng(FILE *fp, Bitmap& bitmap, ImageAttributes& attributes)
+bool LoadBitmapFromPng( FILE *fp, Bitmap& bitmap, ImageAttributes& attributes, const ResourceLoadingClient& client )
{
png_structp png = NULL;
png_infop info = NULL;
namespace SlpPlatform
{
+class ResourceLoadingClient;
+
namespace Png
{
const unsigned char MAGIC_BYTE_1 = 0x89;
* @param[in] attributes Describes the dimensions, pixel format and other details for loading the image data
* @return true if file decoded successfully, false otherwise
*/
-bool LoadBitmapFromPng(FILE *fp, Integration::Bitmap& bitmap, ImageAttributes& attributes);
+bool LoadBitmapFromPng( FILE *fp, Integration::Bitmap& bitmap, ImageAttributes& attributes, const ResourceLoadingClient& client );
/**
* Loads the header of a PNG file and fills in the width and height appropriately.
}// end unnamed namespace
-bool LoadBitmapFromWbmp(FILE *fp, Integration::Bitmap& bitmap, ImageAttributes& attributes)
+bool LoadBitmapFromWbmp( FILE *fp, Integration::Bitmap& bitmap, ImageAttributes& attributes, const ResourceLoadingClient& client )
{
if(fp == NULL)
namespace SlpPlatform
{
-bool LoadBitmapFromWbmp(FILE *fp, Integration::Bitmap& bitmap, ImageAttributes& attributes);
+class ResourceLoadingClient;
+
+bool LoadBitmapFromWbmp( FILE *fp, Integration::Bitmap& bitmap, ImageAttributes& attributes, const ResourceLoadingClient& client );
bool LoadWbmpHeader(FILE *fp, const ImageAttributes& attributes, unsigned int &width, unsigned int &height );
--- /dev/null
+#ifndef _DALI_PLATFORM_RESOURCE_LOADING_CLIENT_H_
+#define _DALI_PLATFORM_RESOURCE_LOADING_CLIENT_H_
+/*
+ * Copyright (c) 2014 Samsung Electronics Co., Ltd.
+ *
+ * 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.
+ *
+ */
+
+// INTERNAL INCLUDES
+
+// EXTERNAL INCLUDES
+
+namespace Dali
+{
+namespace SlpPlatform
+{
+
+/**
+ * @brief Abstract interface to the caller of a low-level resource loading
+ * function such as a loader for an image format.
+ */
+class ResourceLoadingClient
+{
+public:
+ /**
+ * @brief Check whether the current request has been cancelled.
+ *
+ * This will throw an exception to unwind the stack if the current request
+ * has been cancelled.
+ *
+ * @note Only place calls to this function at exception-safe locations in loader code.
+ **/
+ virtual void InterruptionPoint() const = 0;
+
+protected:
+ /** Construction is restricted to derived / implementing classes. */
+ ResourceLoadingClient() {}
+ /** Destruction of an object through this interface is not allowed. */
+ ~ResourceLoadingClient() {}
+
+private:
+ ResourceLoadingClient( const ResourceLoadingClient& rhs );
+ ResourceLoadingClient& operator =( ResourceLoadingClient& rhs );
+};
+
+} /* namespace SlpPlatform */
+} /* namespace Dali */
+
+#endif /* _DALI_PLATFORM_RESOURCE_LOADING_CLIENT_H_ */
#include <dali/integration-api/debug.h>
#include "resource-thread-base.h"
#include "slp-logging.h"
+#include "atomics.h"
using namespace std;
using namespace Dali::Integration;
const char * const IDLE_PRIORITY_ENVIRONMENT_VARIABLE_NAME = "DALI_RESOURCE_THREAD_IDLE_PRIORITY"; ///@Todo Move this to somewhere that other environment variables are declared and document it there.
} // unnamed namespace
-ResourceThreadBase::ResourceThreadBase(ResourceLoader& resourceLoader)
-: mResourceLoader( resourceLoader ), mCurrentRequestId( NO_REQUEST ), mPaused( false )
+/** Thrown by InterruptionPoint() to abort a request early. */
+class CancelRequestException {};
+
+ResourceThreadBase::ResourceThreadBase( ResourceLoader& resourceLoader ) :
+ mResourceLoader( resourceLoader ),
+ mCurrentRequestId( NO_REQUEST ),
+ mCancelRequestId( NO_REQUEST ),
+ mPaused( false )
{
#if defined(DEBUG_ENABLED)
mLogFilter = Debug::Filter::New(Debug::Concise, false, "LOG_RESOURCE_THREAD_BASE");
}
}
-void ResourceThreadBase::CancelRequest( Integration::ResourceId resourceId )
+// Called from outer thread.
+void ResourceThreadBase::CancelRequest( const Integration::ResourceId resourceId )
{
+ bool found = false;
+ DALI_LOG_INFO( mLogFilter, Debug::Verbose, "%s: %u.\n", __FUNCTION__, unsigned(resourceId) );
+
+ // Eliminate the cancelled request from the request queue if it is in there:
{
// Lock while searching and removing from the request queue:
unique_lock<mutex> lock( mMutex );
- // See if the request is already launched as the current job on the thread:
- //if( mCurrentRequestId == resourceId )
- //{
- // mThread->interrupt();
- //}
- // Check the pending requests to be cancelled:
- //else
+ for( RequestQueueIter iterator = mQueue.begin();
+ iterator != mQueue.end();
+ ++iterator )
{
- for( RequestQueueIter iterator = mQueue.begin();
- iterator != mQueue.end();
- ++iterator )
+ if( ((*iterator).first).GetId() == resourceId )
{
- if( ((*iterator).first).GetId() == resourceId )
- {
- iterator = mQueue.erase( iterator );
- break;
- }
+ iterator = mQueue.erase( iterator );
+ found = true;
+ break;
}
}
}
+
+ // Remember the cancelled id for the worker thread to poll at one of its points
+ // of interruption:
+ if( !found )
+ {
+ Dali::Internal::AtomicWriteToCacheableAlignedAddress( &mCancelRequestId, resourceId );
+ DALI_LOG_INFO( mLogFilter, Debug::Concise, "%s: Cancelling in-flight resource (%u).\n", __FUNCTION__, unsigned(resourceId) );
+ }
+}
+
+// Called from worker thread.
+void ResourceThreadBase::InterruptionPoint() const
+{
+ const Integration::ResourceId cancelled = Dali::Internal::AtomicReadFromCacheableAlignedAddress( &mCancelRequestId );
+ const Integration::ResourceId current = mCurrentRequestId;
+
+ if( current == cancelled )
+ {
+ DALI_LOG_INFO( mLogFilter, Debug::Concise, "%s: Cancelled in-flight resource (%u).\n", __FUNCTION__, unsigned(cancelled) );
+ throw CancelRequestException();
+ }
}
void ResourceThreadBase::Pause()
}
}
- catch( boost::thread_interrupted& ex )
+ catch( CancelRequestException& ex )
{
- // No problem, thread was just interrupted from the outside to cancel an in-flight request.
- boost::thread_interrupted* disableUnusedVarWarning = &ex;
+ // No problem: a derived class deliberately threw to abort an in-flight request
+ // that was cancelled.
+ DALI_LOG_INFO( mLogFilter, Debug::Concise, "%s: Caught cancellation exception for resource (%u).\n", __FUNCTION__, unsigned(mCurrentRequestId) );
+ CancelRequestException* disableUnusedVarWarning = &ex;
ex = *disableUnusedVarWarning;
- // Temporary logging of an unexpected boost::thread_interrupted exception:
- DALI_LOG_ERROR( "boost::thread_interrupted caught in resource thread in build with late cancellation disabled (should not happen). Aborting request with id %u.\n", unsigned(mCurrentRequestId) );
}
- // Since we have an exception handler here anyway, lets catch everything to avoid killing the process:
+ // Catch all exceptions to avoid killing the process, and log the error:
catch( std::exception& ex )
{
const char * const what = ex.what();
DALI_LOG_ERROR( "std::exception caught in resource thread. Aborting request with id %u because of std::exception with reason, \"%s\".\n", unsigned(mCurrentRequestId), what ? what : "null" );
-
}
catch( Dali::DaliException& ex )
{
{
unique_lock<mutex> lock( mMutex );
- // Clear the previously current request:
- mCurrentRequestId = NO_REQUEST;
-
if( mQueue.empty() || mPaused == true )
{
// Waiting for a wake up from resource loader control thread
}
break;
}
-
- // Clear the interruption status for derived classes that don't implement on-the-fly cancellation yet:
- boost::this_thread::interruption_point(); ///@warning This can throw an exception.
- // To support cancellation of an in-flight resource, place the above line at key points in derived
- // resource thread classes and the loading / decoding / saving code that they call.
- // See resource-thread-image.cpp and loader-jpeg-turbo.cpp for a conservative example of its use.
- ///@note: The above line will throw an exception so only place it in exception-safe locations.
}
}
*
*/
+// INTERNAL INCLUDES
#include "resource-loader.h"
-#include <deque>
-#include <boost/thread.hpp>
-
+#include "resource-loading-client.h"
#include <dali/integration-api/platform-abstraction.h>
#include <dali/integration-api/resource-cache.h>
+// EXTERNAL INCLUDES
+#include <deque>
+#include <boost/thread.hpp>
+
namespace Dali
{
/**
* Resource loader worker thread
*/
-class ResourceThreadBase
+class ResourceThreadBase : public ResourceLoadingClient
{
public:
// typedefs and enums
*/
virtual void Save(const Integration::ResourceRequest& request) = 0;
+ /**
+ * @brief Cancels current resource request if it matches the one latched to be cancelled.
+ *
+ * @copydoc ResourceLoadingClient::InterruptionPoint
+ */
+ virtual void InterruptionPoint() const;
+
protected:
ResourceLoader& mResourceLoader;
boost::thread* mThread; ///< thread instance
boost::condition_variable mCondition; ///< condition variable
boost::mutex mMutex; ///< used to protect mQueue
RequestQueue mQueue; ///< Request queue
- Integration::ResourceId mCurrentRequestId; ///< Request being processed on thread
+private:
+ Integration::ResourceId mCurrentRequestId; ///< Current request, set by worker thread
+ volatile Integration::ResourceId mCancelRequestId; ///< Request to be cancelled on thread: written by external thread and read by worker.
bool mPaused; ///< Whether to process work in mQueue
#if defined(DEBUG_ENABLED)
namespace
{
-typedef bool (*LoadBitmapFunction)(FILE*, Bitmap&, ImageAttributes&);
+typedef bool (*LoadBitmapFunction)(FILE*, Bitmap&, ImageAttributes&, const ResourceLoadingClient& );
typedef bool (*LoadBitmapHeaderFunction)(FILE*, const ImageAttributes&, unsigned int&, unsigned int&);
/*
if (GetBitmapLoaderFunctions(fp, function, header))
{
- result = function(fp, *bitmap, attributes);
+ result = function(fp, *bitmap, attributes, *this);
if (result)
{
DALI_ASSERT_DEBUG(request.GetType()->id == ResourceBitmap);
}
-
} // namespace SlpPlatform
} // namespace Dali
*/
virtual void Save(const Integration::ResourceRequest& request);
-private:
-
}; // class ResourceThreadImage
} // namespace SlpPlatform
namespace
{
-typedef bool (*LoadBitmapFunction)(FILE*, Bitmap&, ImageAttributes&); ///@ToDo: Make attributes a const reference?
+typedef bool (*LoadBitmapFunction)( FILE*, Bitmap&, ImageAttributes&, const ResourceLoadingClient& ); ///@ToDo: Make attributes a const reference?
typedef bool (*LoadBitmapHeaderFunction)(FILE*, const ImageAttributes& attrs, unsigned int& width, unsigned int& height );
/**
result = ConvertStreamToBitmap( *request.GetType(), request.GetPath(), fp, bitmap );
// Last chance to interrupt a cancelled load before it is reported back to clients
// which have already stopped tracking it:
- boost::this_thread::interruption_point(); // Note: This can throw an exception.
+ InterruptionPoint(); // Note: This can throw an exception.
if( result && bitmap )
{
// Construct LoadedResource and ResourcePointer for image data
ImageAttributes attributes = resType.imageAttributes;
// Check for cancellation now we have hit the filesystem, done some allocation, and burned some cycles:
- boost::this_thread::interruption_point(); // Note: This can throw an exception.
+ InterruptionPoint(); // Note: This can throw an exception.
- result = function( fp, *bitmap, attributes );
+ result = function( fp, *bitmap, attributes, *this );
if (!result)
{
// Make a new bitmap with the central part of the loaded one if required:
if( scanlinesToTrim > 0 || columnsToTrim > 0 ) ///@ToDo: Make this test a bit fuzzy (allow say a 5% difference).
{
- boost::this_thread::interruption_point(); //< Check for cancellation before doing the heavy lifting (Note: This can throw an exception.)
+ InterruptionPoint(); // Note: This can throw an exception.
const unsigned newWidth = loadedWidth - 2 * columnsToTrim;
const unsigned newHeight = loadedHeight - 2 * scanlinesToTrim;
return result;
}
-
} // namespace SlpPlatform
} // namespace Dali
FILE * const fp,
Integration::BitmapPtr& ptr );
-
}; // class ResourceThreadImage
} // namespace SlpPlatform