Enable late cancelation of resource loads already in flight 43/24443/10
authorAndrew Cox <andrew.cox@partner.samsung.com>
Tue, 24 Jun 2014 15:36:25 +0000 (16:36 +0100)
committerAndrew Cox <andrew.cox@partner.samsung.com>
Thu, 17 Jul 2014 14:15:19 +0000 (15:15 +0100)
Change-Id: If08cbfdc9d7dde4204c3b27f0d50db91e926784d
Signed-off-by: Andrew Cox <andrew.cox@partner.samsung.com>
26 files changed:
automated-tests/.gitignore
automated-tests/src/dali-adaptor-internal/image-loaders.cpp
automated-tests/src/dali-adaptor-internal/image-loaders.h
automated-tests/src/dali-platform-abstraction/utc-image-loading.cpp
platform-abstractions/portable/atomics.h [new file with mode: 0644]
platform-abstractions/slp/resource-loader/loader-bmp.cpp
platform-abstractions/slp/resource-loader/loader-bmp.h
platform-abstractions/slp/resource-loader/loader-gif.cpp
platform-abstractions/slp/resource-loader/loader-gif.h
platform-abstractions/slp/resource-loader/loader-ico.cpp
platform-abstractions/slp/resource-loader/loader-ico.h
platform-abstractions/slp/resource-loader/loader-jpeg-turbo.cpp
platform-abstractions/slp/resource-loader/loader-jpeg.h
platform-abstractions/slp/resource-loader/loader-ktx.cpp
platform-abstractions/slp/resource-loader/loader-ktx.h
platform-abstractions/slp/resource-loader/loader-png.cpp
platform-abstractions/slp/resource-loader/loader-png.h
platform-abstractions/slp/resource-loader/loader-wbmp.cpp
platform-abstractions/slp/resource-loader/loader-wbmp.h
platform-abstractions/slp/resource-loader/resource-loading-client.h [new file with mode: 0644]
platform-abstractions/slp/resource-loader/resource-thread-base.cpp
platform-abstractions/slp/resource-loader/resource-thread-base.h
platform-abstractions/slp/resource-loader/resource-thread-distance-field.cpp
platform-abstractions/slp/resource-loader/resource-thread-distance-field.h
platform-abstractions/slp/resource-loader/resource-thread-image.cpp
platform-abstractions/slp/resource-loader/resource-thread-image.h

index f039d8a..94db43f 100644 (file)
@@ -2,3 +2,4 @@
 build
 build.log
 tct*core.h
+rules.mk
\ No newline at end of file
index 8c01772..94a5ae3 100644 (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 )
 {
@@ -106,7 +116,7 @@ void TestImageLoading( const ImageDetails& image, const LoadFunctions& functions
 
 
   // 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 );
 
@@ -133,7 +143,7 @@ void DumpImageBufferToTempFile( std::string filename, std::string targetFilename
   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() );
 
index 6e4e14c..952d8f1 100644 (file)
@@ -20,6 +20,7 @@
 
 #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
@@ -81,7 +82,7 @@ private:
  */
 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 );
index 8bcc96f..4e41883 100644 (file)
@@ -15,6 +15,7 @@
  *
  */
 
+#include <unistd.h>
 #include <iostream>
 #include <stdlib.h>
 #include <dali/dali.h>
@@ -34,6 +35,13 @@ namespace
  * 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;
 
@@ -45,20 +53,83 @@ const char* const VALID_IMAGES[] = {
   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)
@@ -98,7 +169,8 @@ int UtcDaliLoadCompletion(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 );
   }
 
@@ -110,7 +182,7 @@ int UtcDaliLoadCompletion(void)
   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 );
   }
@@ -118,3 +190,156 @@ int UtcDaliLoadCompletion(void)
   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;
+}
diff --git a/platform-abstractions/portable/atomics.h b/platform-abstractions/portable/atomics.h
new file mode 100644 (file)
index 0000000..bf5d8ce
--- /dev/null
@@ -0,0 +1,73 @@
+#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_ */
index 3653b12..436986a 100644 (file)
@@ -1050,7 +1050,7 @@ bool LoadBmpHeader(FILE *fp, const ImageAttributes& attributes, unsigned int &wi
   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)
index c8317b2..ec41983 100644 (file)
@@ -25,7 +25,7 @@ namespace Dali
 
 namespace Integration
 {
-  class Bitmap;
+class Bitmap;
 }
 
 struct ImageAttributes;
@@ -33,6 +33,8 @@ struct ImageAttributes;
 namespace SlpPlatform
 {
 
+class ResourceLoadingClient;
+
 namespace Bmp
 {
 const unsigned char MAGIC_BYTE_1 = 0x42;
@@ -47,7 +49,7 @@ const unsigned char MAGIC_BYTE_2 = 0x4D;
  * @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.
index 05312e4..c000f1c 100644 (file)
@@ -272,7 +272,7 @@ bool LoadGifHeader(FILE *fp, const ImageAttributes& attributes, unsigned int &wi
   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.
 
index 836ebdc..278ac37 100644 (file)
@@ -33,6 +33,8 @@ struct ImageAttributes;
 namespace SlpPlatform
 {
 
+class ResourceLoadingClient;
+
 namespace Gif
 {
 const unsigned char MAGIC_BYTE_1 = 0x47;
@@ -48,7 +50,7 @@ const unsigned char MAGIC_BYTE_2 = 0x49;
  * @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.
index 76f52b1..578ede6 100644 (file)
@@ -325,7 +325,7 @@ bool LoadIcoHeader(FILE *fp, const ImageAttributes& attributes, unsigned int &wi
   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;
index 7228ee8..9b7155e 100644 (file)
@@ -31,6 +31,9 @@ struct ImageAttributes;
 
 namespace SlpPlatform
 {
+
+class ResourceLoadingClient;
+
 namespace Ico
 {
 //00 00 01 00 01 00 20 20
@@ -38,7 +41,7 @@ const unsigned char MAGIC_BYTE_1 = 0x00;
 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 );
 
index cdaa224..2951398 100755 (executable)
  *
  */
 
+// 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
 {
@@ -236,7 +238,7 @@ bool LoadJpegHeader( FILE *fp, unsigned int &width, unsigned int &height )
   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) |
@@ -293,7 +295,7 @@ bool LoadBitmapFromJpeg( FILE *fp, Bitmap& bitmap, ImageAttributes& attributes )
   }
 
   // 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());
 
@@ -353,7 +355,7 @@ bool LoadBitmapFromJpeg( FILE *fp, Bitmap& bitmap, ImageAttributes& attributes )
   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 )
@@ -371,7 +373,7 @@ bool LoadBitmapFromJpeg( FILE *fp, Bitmap& bitmap, ImageAttributes& attributes )
   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;
index ddb00c6..ec97537 100644 (file)
@@ -36,6 +36,8 @@ struct ImageAttributes;
 namespace SlpPlatform
 {
 
+class ResourceLoadingClient;
+
 namespace Jpeg
 {
 const unsigned char MAGIC_BYTE_1 = 0xFF;
@@ -50,7 +52,7 @@ const unsigned char MAGIC_BYTE_2 = 0xD8;
  * @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.
index 22bdc57..da1c823 100755 (executable)
@@ -304,7 +304,7 @@ bool LoadKtxHeader(FILE * const fp, const ImageAttributes& attributes, unsigned
 }
 
 // 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);
index a1354e6..9b18007 100644 (file)
@@ -33,6 +33,8 @@ struct ImageAttributes;
 namespace SlpPlatform
 {
 
+class ResourceLoadingClient;
+
 namespace Ktx
 {
 const unsigned char MAGIC_BYTE_1 = 0xAB;
@@ -48,7 +50,7 @@ const unsigned char MAGIC_BYTE_2 = 0x4B;
  * @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.
index 81d550a..eadc3da 100644 (file)
@@ -141,7 +141,7 @@ bool LoadPngHeader(FILE *fp, const ImageAttributes& attributes, unsigned int &wi
   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;
index 2217c68..698d6ad 100644 (file)
@@ -35,6 +35,8 @@ struct ImageAttributes;
 namespace SlpPlatform
 {
 
+class ResourceLoadingClient;
+
 namespace Png
 {
 const unsigned char MAGIC_BYTE_1 = 0x89;
@@ -49,7 +51,7 @@ const unsigned char MAGIC_BYTE_2 = 0x50;
  * @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.
index 1cac543..9759d8f 100755 (executable)
@@ -94,7 +94,7 @@ int extractMultiByteInteger(unsigned int *data, void *map, size_t length, size_t
 
 }// 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)
index 8565310..dfc64cf 100755 (executable)
@@ -32,7 +32,9 @@ struct ImageAttributes;
 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 );
 
diff --git a/platform-abstractions/slp/resource-loader/resource-loading-client.h b/platform-abstractions/slp/resource-loader/resource-loading-client.h
new file mode 100644 (file)
index 0000000..df38279
--- /dev/null
@@ -0,0 +1,60 @@
+#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_ */
index 3a99323..b5f102d 100644 (file)
@@ -18,6 +18,7 @@
 #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;
@@ -38,8 +39,14 @@ namespace
 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");
@@ -94,32 +101,50 @@ void ResourceThreadBase::AddRequest(const ResourceRequest& request, const Reques
   }
 }
 
-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()
@@ -175,21 +200,20 @@ void ResourceThreadBase::ThreadLoop()
       }
     }
 
-    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 )
     {
@@ -207,9 +231,6 @@ void ResourceThreadBase::WaitForRequests()
 {
   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
@@ -261,13 +282,6 @@ void ResourceThreadBase::ProcessNextRequest()
       }
       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.
   }
 }
 
index ffabc67..aabbfa9 100644 (file)
  *
  */
 
+// 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
 {
 
@@ -34,7 +37,7 @@ namespace SlpPlatform
 /**
  * Resource loader worker thread
  */
-class ResourceThreadBase
+class ResourceThreadBase : public ResourceLoadingClient
 {
 public:
   // typedefs and enums
@@ -134,13 +137,22 @@ protected:
    */
   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)
index 5cbada8..56231ae 100644 (file)
@@ -41,7 +41,7 @@ namespace SlpPlatform
 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&);
 
 /*
@@ -164,7 +164,7 @@ void ResourceThreadDistanceField::Load(const ResourceRequest& request)
 
     if (GetBitmapLoaderFunctions(fp, function, header))
     {
-      result = function(fp, *bitmap, attributes);
+      result = function(fp, *bitmap, attributes, *this);
 
       if (result)
       {
@@ -264,7 +264,6 @@ void ResourceThreadDistanceField::Save(const Integration::ResourceRequest& reque
   DALI_ASSERT_DEBUG(request.GetType()->id == ResourceBitmap);
 }
 
-
 } // namespace SlpPlatform
 
 } // namespace Dali
index c74966e..8884c57 100644 (file)
@@ -61,8 +61,6 @@ private:
    */
   virtual void Save(const Integration::ResourceRequest& request);
 
-private:
-
 }; // class ResourceThreadImage
 
 } // namespace SlpPlatform
index 8a5c9af..3d25b1b 100755 (executable)
@@ -46,7 +46,7 @@ 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 );
 
 /**
@@ -382,7 +382,7 @@ void ResourceThreadImage::Load(const ResourceRequest& request)
     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
@@ -501,9 +501,9 @@ bool ResourceThreadImage::ConvertStreamToBitmap(const ResourceType& resourceType
       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)
       {
@@ -550,7 +550,7 @@ bool ResourceThreadImage::ConvertStreamToBitmap(const ResourceType& resourceType
           // 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;
@@ -595,7 +595,6 @@ bool ResourceThreadImage::ConvertStreamToBitmap(const ResourceType& resourceType
   return result;
 }
 
-
 } // namespace SlpPlatform
 
 } // namespace Dali
index 60152a4..e9a4b36 100644 (file)
@@ -103,7 +103,6 @@ private:
                               FILE * const fp,
                               Integration::BitmapPtr& ptr );
 
-
 }; // class ResourceThreadImage
 
 } // namespace SlpPlatform