Merge "Merge branch 'devel/new_mesh' into devel/master" into devel/master
[platform/core/uifw/dali-core.git] / dali / internal / event / resources / resource-client.cpp
1 /*
2  * Copyright (c) 2014 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17
18 // INTERNAL INCLUDES
19 #include <dali/internal/event/resources/resource-client.h>
20 #include <dali/devel-api/common/map-wrapper.h>
21
22 #include <dali/integration-api/resource-request.h>
23 #include <dali/integration-api/debug.h>
24
25 #include <dali/internal/event/common/event-thread-services.h>
26 #include <dali/internal/event/common/stage-impl.h>
27 #include <dali/internal/event/images/image-impl.h>
28 #include <dali/internal/update/resources/resource-manager.h>
29
30
31 namespace Dali
32 {
33 namespace Internal
34 {
35 using namespace Dali::Integration;
36
37 typedef std::map<ResourceId, ResourceTicket*>        TicketContainer;
38 typedef TicketContainer::iterator                    TicketContainerIter;
39 typedef TicketContainer::size_type                   TicketContainerSize;
40 typedef std::pair<ResourceId, ResourceTicket*>       TicketPair;
41
42 typedef std::map<ResourceId, Bitmap*>                BitmapCache;
43 typedef BitmapCache::iterator                        BitmapCacheIter;
44
45 struct ResourceClient::Impl
46 {
47   Impl(ResourcePolicy::DataRetention dataRetentionPolicy)
48   : mNextId(0),
49     mDataRetentionPolicy( dataRetentionPolicy )
50   {
51   }
52
53   ResourceId       mNextId;
54   TicketContainer  mTickets;
55   BitmapCache      mBitmaps;
56   ResourcePolicy::DataRetention mDataRetentionPolicy;
57 };
58
59 ResourceClient::ResourceClient( ResourceManager& resourceManager,
60                                 EventThreadServices& eventThreadServices,
61                                 ResourcePolicy::DataRetention dataRetentionPolicy)
62 : mResourceManager(resourceManager),
63   mEventThreadServices(eventThreadServices)
64 {
65   mImpl = new ResourceClient::Impl(dataRetentionPolicy);
66   mResourceManager.SetClient(*this);
67 }
68
69 ResourceClient::~ResourceClient()
70 {
71   // Guard to allow handle destruction after Core has been destroyed
72   if ( Stage::IsInstalled() )
73   {
74     for (TicketContainerIter iter = mImpl->mTickets.begin(); iter != mImpl->mTickets.end(); ++iter)
75     {
76       (*iter).second->StopLifetimeObservation();
77     }
78   }
79   delete mImpl;
80 }
81
82 ResourcePolicy::DataRetention ResourceClient::GetResourceDataRetentionPolicy()
83 {
84   return mImpl->mDataRetentionPolicy;
85 }
86
87 ResourceTicketPtr ResourceClient::RequestResource(
88   const ResourceType& type,
89   const std::string& path,
90   LoadResourcePriority priority )
91 {
92   ResourceTicketPtr newTicket;
93   ResourceTypePath typePath(type, path);
94   ResourceId newId = 0;
95
96   // Create the ticket first
97   // NOTE: pre-increment, otherwise we get 0 for first one.
98   newId = ++(mImpl->mNextId);
99
100   switch (type.id)
101   {
102     case ResourceBitmap:
103     {
104       const BitmapResourceType& bitmapResource = static_cast <const BitmapResourceType&> (type);
105       // image tickets will cache the requested parameters, which are updated on successful loading
106       ImageTicket* imageTicket = new ImageTicket(*this, newId, typePath);
107       imageTicket->mAttributes.Reset( bitmapResource.size, bitmapResource.scalingMode, bitmapResource.samplingMode, bitmapResource.orientationCorrection );
108       newTicket = imageTicket;
109       break;
110     }
111
112     case ResourceNativeImage:
113     {
114       const NativeImageResourceType& nativeResource = static_cast <const NativeImageResourceType&> (type);
115       // image tickets will cache the requested parameters, which are updated on successful loading
116       ImageTicket* imageTicket = new ImageTicket(*this, newId, typePath);
117       imageTicket->mAttributes.SetSize( nativeResource.imageDimensions.GetWidth(), nativeResource.imageDimensions.GetHeight() );
118       newTicket = imageTicket;
119       break;
120     }
121     case ResourceTargetImage:
122     case ResourceShader:
123     {
124       newTicket = new ResourceTicket(*this, newId, typePath);
125       break;
126     }
127   }
128
129   mImpl->mTickets.insert(TicketPair(newId, newTicket.Get()));
130
131   DALI_LOG_INFO(Debug::Filter::gResource, Debug::General, "ResourceClient: RequestResource(path:%s type.id:%d) newId:%u\n", path.c_str(), type.id, newId);
132
133   RequestLoadResourceMessage( mEventThreadServices, mResourceManager, newId, typePath, priority );
134   return newTicket;
135 }
136
137 ResourceTicketPtr ResourceClient::DecodeResource(
138   const ResourceType& type,
139   RequestBufferPtr buffer,
140   LoadResourcePriority priority )
141 {
142   DALI_ASSERT_DEBUG( type.id == ResourceBitmap && "Only bitmap resources are currently decoded from memory buffers. It should be easy to expand to other resource types though. The public API function at the front and the resource thread at the back end are all that should need to be changed. The code in the middle should be agnostic to the the resource type it is conveying.\n" );
143   DALI_ASSERT_DEBUG( buffer.Get() && "Null resource buffer passed for decoding." );
144   ResourceTicketPtr newTicket;
145   if( buffer.Get() ) //< Check to avoid SEGV on a null pointer.
146   {
147     ResourceTypePath typePath( type, "" );
148     ResourceId newId = 0;
149
150     // Create the correct ticket type for the resource:
151     switch (type.id)
152     {
153       case ResourceBitmap:
154       {
155         // NOTE: pre-increment, otherwise we get 0 for first one.
156         newId = ++(mImpl->mNextId);
157         const BitmapResourceType& bitmapResource = static_cast <const BitmapResourceType&> ( type );
158         // Image tickets will cache the requested parameters, which are updated on successful loading
159         ImageTicket* imageTicket = new ImageTicket( *this, newId, typePath );
160         imageTicket->mAttributes.Reset( bitmapResource.size, bitmapResource.scalingMode, bitmapResource.samplingMode, bitmapResource.orientationCorrection );;
161         newTicket = imageTicket;
162         break;
163       }
164
165       // FALLTHROUGH:
166       case ResourceNativeImage:
167       case ResourceTargetImage:
168       case ResourceShader:
169       {
170         DALI_LOG_ERROR( "Unsupported resource type passed for decoding from a memory buffer." );
171       }
172     }
173
174     if( newTicket )
175     {
176       mImpl->mTickets.insert(TicketPair(newId, newTicket.Get()));
177       DALI_LOG_INFO(Debug::Filter::gResource, Debug::General, "ResourceClient: DecodeResource( type.id:%d ) newId:%u\n", type.id, newId);
178
179       RequestDecodeResourceMessage( mEventThreadServices, mResourceManager, newId, typePath, buffer, priority );
180     }
181   }
182   return newTicket;
183 }
184
185 ResourceTicketPtr ResourceClient::LoadShader( ShaderResourceType& type,
186                                               const std::string& path )
187 {
188   ResourceTicketPtr newTicket;
189
190   const ResourceId newId = ++(mImpl->mNextId);
191
192   ResourceTypePath typePath(type, path);
193   newTicket = new ResourceTicket(*this, newId, typePath);
194
195   mImpl->mTickets.insert(TicketPair(newId, newTicket.Get()));
196
197   DALI_LOG_INFO(Debug::Filter::gResource, Debug::General, "ResourceClient: LoadShader(path:%s) newId:%u\n", path.c_str(), newId);
198
199   RequestLoadShaderMessage( mEventThreadServices, mResourceManager, newId, typePath );
200   return newTicket;
201 }
202
203 bool ResourceClient::ReloadResource( ResourceId id, bool resetFinishedStatus, LoadResourcePriority priority )
204 {
205   DALI_LOG_INFO(Debug::Filter::gResource, Debug::General, "ResourceClient: ReloadResource(Id: %u)\n", id);
206
207   bool resourceExists = false;
208   TicketContainerIter ticketIter;
209   ticketIter = mImpl->mTickets.find(id);
210
211   if(ticketIter != mImpl->mTickets.end())
212   {
213     resourceExists = true;
214     // The ticket is already being observed
215     ResourceTicket* ticket = ticketIter->second;
216     DALI_ASSERT_DEBUG(ticket && "Null ticket for tracked resource request." );
217     const ResourceTypePath * const typePathPtr = &ticket->GetTypePath();
218     DALI_ASSERT_DEBUG( typePathPtr );
219     RequestReloadResourceMessage( mEventThreadServices, mResourceManager, id, *typePathPtr, priority, resetFinishedStatus );
220   }
221   else
222   {
223     DALI_LOG_ERROR ("Resource %d does not exist\n", id);
224   }
225   return resourceExists;
226 }
227
228 void ResourceClient::SaveResource( ResourceTicketPtr ticket, const std::string& url )
229 {
230   DALI_ASSERT_DEBUG( ticket );
231
232   DALI_LOG_INFO(Debug::Filter::gResource, Debug::General, "ResourceClient: SaveResource(Id: %u, path:%s)\n", ticket->GetId(), url.c_str());
233
234   const ResourceTypePath * const typePathPtr = &ticket->GetTypePath();
235   if( typePathPtr )
236   {
237     if( 0 != url.length() )
238     {
239       ResourceTypePath typePath( *(typePathPtr->type), url );
240       RequestSaveResourceMessage( mEventThreadServices, mResourceManager, ticket->GetId(), typePath );
241     }
242     else
243     {
244       ResourceTypePath typePath( *typePathPtr );
245       RequestSaveResourceMessage( mEventThreadServices, mResourceManager, ticket->GetId(), typePath );
246     }
247   }
248 }
249
250 ResourceTicketPtr ResourceClient::RequestResourceTicket( ResourceId id )
251 {
252   DALI_LOG_INFO(Debug::Filter::gResource, Debug::General, "ResourceClient: RequestResourceTicket(Id: %u)\n", id);
253
254   ResourceTicketPtr ticket;
255
256   TicketContainerIter ticketIter = mImpl->mTickets.find( id );
257
258   if ( mImpl->mTickets.end() != ticketIter )
259   {
260     ticket = ticketIter->second;
261   }
262
263   return ticket;
264 }
265
266 ImageTicketPtr ResourceClient::AllocateBitmapImage( unsigned int width,
267                                                     unsigned int height,
268                                                     unsigned int bufferWidth,
269                                                     unsigned int bufferHeight,
270                                                     Pixel::Format pixelformat )
271 {
272   /* buffer is available via public-api, therefore not discardable */
273   Bitmap* const bitmap = Bitmap::New( Bitmap::BITMAP_2D_PACKED_PIXELS, ResourcePolicy::RETAIN );
274   Bitmap::PackedPixelsProfile* const packedBitmap = bitmap->GetPackedPixelsProfile();
275   DALI_ASSERT_DEBUG(packedBitmap);
276
277   packedBitmap->ReserveBuffer(pixelformat, width, height, bufferWidth, bufferHeight);
278   DALI_ASSERT_DEBUG(bitmap->GetBuffer() != 0);
279   DALI_ASSERT_DEBUG(bitmap->GetBufferSize() >= width * height);
280
281   ImageTicketPtr ticket = AddBitmapImage(bitmap);
282
283   DALI_ASSERT_DEBUG(bitmap->GetBuffer() != 0);
284   DALI_ASSERT_DEBUG(bitmap->GetBufferSize() >= width * height);
285   return ticket;
286 }
287
288 ImageTicketPtr ResourceClient::AddBitmapImage(Bitmap* bitmap)
289 {
290   DALI_ASSERT_DEBUG( bitmap != NULL );
291
292   ImageTicketPtr newTicket;
293
294   const ResourceId newId = ++(mImpl->mNextId);
295
296   ImageAttributes imageAttributes = ImageAttributes::New(bitmap->GetImageWidth(), bitmap->GetImageHeight());
297   BitmapResourceType bitmapResourceType( ImageDimensions::FromFloatVec2( imageAttributes.GetSize() ) ); // construct first as no copy ctor (needed to bind ref to object)
298   ResourceTypePath typePath(bitmapResourceType, "");
299   newTicket = new ImageTicket(*this, newId, typePath);
300   newTicket->mAttributes = imageAttributes;
301   newTicket->LoadingSucceeded();
302
303   mImpl->mTickets.insert(TicketPair(newId, newTicket.Get()));
304
305   // Store bitmap for immediate access.
306   mImpl->mBitmaps[newId] = bitmap;
307
308   DALI_LOG_INFO(Debug::Filter::gResource, Debug::General, "ResourceClient: AddBitmapImage() New id = %u\n", newId);
309   RequestAddBitmapImageMessage( mEventThreadServices, mResourceManager, newId, bitmap );
310
311   return newTicket;
312 }
313
314 ResourceTicketPtr ResourceClient::AddNativeImage ( NativeImageInterface& resourceData )
315 {
316   ImageTicketPtr newTicket;
317
318   const ResourceId newId = ++(mImpl->mNextId);
319   NativeImageResourceType nativeImageResourceType; // construct first as no copy ctor (needed to bind ref to object)
320   ResourceTypePath typePath(nativeImageResourceType, "");
321   newTicket = new ImageTicket(*this, newId, typePath);
322   newTicket->mAttributes = ImageAttributes::New(resourceData.GetWidth(),
323                                                 resourceData.GetHeight());
324   newTicket->LoadingSucceeded();
325
326   mImpl->mTickets.insert(TicketPair(newId, newTicket.Get()));
327
328   DALI_LOG_INFO(Debug::Filter::gResource, Debug::General, "ResourceClient: AddNativeImage() New id = %u\n", newId);
329
330   RequestAddNativeImageMessage( mEventThreadServices, mResourceManager, newId, &resourceData );
331
332   return newTicket;
333 }
334
335 ImageTicketPtr ResourceClient::AddFrameBufferImage ( unsigned int width, unsigned int height, Pixel::Format pixelFormat )
336 {
337   ImageTicketPtr newTicket;
338
339   const ResourceId newId = ++(mImpl->mNextId);
340
341   ImageAttributes imageAttributes = ImageAttributes::New(width, height);
342   RenderTargetResourceType renderTargetResourceType( ImageDimensions( width, height ) ); // construct first as no copy ctor (needed to bind ref to object)
343   ResourceTypePath typePath(renderTargetResourceType, "");
344   newTicket = new ImageTicket(*this, newId, typePath);
345   newTicket->mAttributes = imageAttributes;
346   newTicket->LoadingSucceeded();
347
348   mImpl->mTickets.insert(TicketPair(newId, newTicket.Get()));
349
350   DALI_LOG_INFO(Debug::Filter::gResource, Debug::General, "ResourceClient: AddFrameBufferImage() New id = %u\n", newId);
351   RequestAddFrameBufferImageMessage( mEventThreadServices, mResourceManager, newId, width, height, pixelFormat );
352
353   return newTicket;
354 }
355
356 ImageTicketPtr ResourceClient::AddFrameBufferImage ( NativeImageInterface& nativeImage )
357 {
358   ImageTicketPtr newTicket;
359
360   const ResourceId newId = ++(mImpl->mNextId);
361
362   ImageAttributes imageAttributes = ImageAttributes::New( nativeImage.GetWidth(), nativeImage.GetHeight() );
363   RenderTargetResourceType renderTargetResourceType( ImageDimensions( nativeImage.GetWidth(), nativeImage.GetHeight() ) ); // construct first as no copy ctor (needed to bind ref to object)
364   ResourceTypePath typePath(renderTargetResourceType, "");
365   newTicket = new ImageTicket(*this, newId, typePath);
366   newTicket->mAttributes = imageAttributes;
367   newTicket->LoadingSucceeded();
368
369   mImpl->mTickets.insert(TicketPair(newId, newTicket.Get()));
370
371   DALI_LOG_INFO(Debug::Filter::gResource, Debug::General, "ResourceClient: AddFrameBufferImage() New id = %u\n", newId);
372   RequestAddFrameBufferImageMessage( mEventThreadServices, mResourceManager, newId, &nativeImage );
373
374   return newTicket;
375 }
376
377
378 ResourceTicketPtr ResourceClient::AllocateTexture( unsigned int width,
379                                                    unsigned int height,
380                                                    Pixel::Format pixelformat )
381 {
382   ImageTicketPtr newTicket;
383   const ResourceId newId = ++(mImpl->mNextId);
384
385   ImageAttributes imageAttributes = ImageAttributes::New( width, height);
386   BitmapResourceType bitmapResourceType( ImageDimensions( width, height ) ); // construct first as no copy ctor (needed to bind ref to object)
387   ResourceTypePath typePath(bitmapResourceType, "");
388   newTicket = new ImageTicket(*this, newId, typePath);
389
390   mImpl->mTickets.insert(TicketPair(newId, newTicket.Get()));
391   newTicket->mAttributes = imageAttributes;
392   newTicket->LoadingSucceeded();
393
394   DALI_LOG_INFO(Debug::Filter::gResource, Debug::General, "ResourceClient: AllocateTexture() New id = %u\n", newId);
395
396   RequestAllocateTextureMessage( mEventThreadServices, mResourceManager, newId, width, height, pixelformat );
397
398   return newTicket;
399 }
400
401 void ResourceClient::UpdateBitmapArea( ResourceTicketPtr ticket, RectArea& updateArea )
402 {
403   DALI_ASSERT_DEBUG( ticket );
404
405   RequestUpdateBitmapAreaMessage( mEventThreadServices, mResourceManager, ticket->GetId(), updateArea );
406 }
407
408 void ResourceClient::UploadBitmap( ResourceId destId, ResourceId srcId, std::size_t xOffset, std::size_t yOffset )
409 {
410   RequestUploadBitmapMessage( mEventThreadServices,
411                               mResourceManager,
412                               destId,
413                               srcId,
414                               xOffset,
415                               yOffset );
416 }
417
418
419 void ResourceClient::UploadBitmap( ResourceId destId,Integration::BitmapPtr bitmap, std::size_t xOffset, std::size_t yOffset)
420 {
421   RequestUploadBitmapMessage( mEventThreadServices,
422                               mResourceManager,
423                               destId,
424                               bitmap,
425                               xOffset,
426                               yOffset );
427 }
428
429 Bitmap* ResourceClient::GetBitmap(ResourceTicketPtr ticket)
430 {
431   DALI_ASSERT_DEBUG( ticket );
432
433   Bitmap* bitmap = NULL;
434   BitmapCacheIter iter = mImpl->mBitmaps.find(ticket->GetId());
435
436   if( iter != mImpl->mBitmaps.end() )
437   {
438     bitmap = iter->second;
439   }
440   return bitmap;
441 }
442
443 void ResourceClient::CreateGlTexture( ResourceId id )
444 {
445   RequestCreateGlTextureMessage( mEventThreadServices, mResourceManager, id );
446 }
447
448
449 /********************************************************************************
450  ********************   ResourceTicketLifetimeObserver methods   ****************
451  ********************************************************************************/
452
453 void ResourceClient::ResourceTicketDiscarded(const ResourceTicket& ticket)
454 {
455   const ResourceId deadId = ticket.GetId();
456   const ResourceTypePath& typePath = ticket.GetTypePath();
457
458   // Ensure associated event owned resources are also removed
459   mImpl->mBitmaps.erase(ticket.GetId());
460
461   // The ticket object is dead, remove from tickets container
462   TicketContainerSize erased = mImpl->mTickets.erase(deadId);
463   DALI_ASSERT_DEBUG(erased != 0);
464   (void)erased; // Avoid "unused variable erased" in release builds
465
466   DALI_LOG_INFO(Debug::Filter::gResource, Debug::General, "ResourceClient: ResourceTicketDiscarded() deadId = %u\n", deadId);
467   RequestDiscardResourceMessage( mEventThreadServices, mResourceManager, deadId, typePath.type->id );
468 }
469
470 /********************************************************************************
471  ***********************   Notifications from ResourceManager  ******************
472  ********************************************************************************/
473
474 void ResourceClient::NotifyUploaded( ResourceId id )
475 {
476   DALI_LOG_INFO(Debug::Filter::gResource, Debug::General, "ResourceClient: NotifyUpdated(id:%u)\n", id);
477
478   TicketContainerIter ticketIter = mImpl->mTickets.find(id);
479   if(ticketIter != mImpl->mTickets.end())
480   {
481     ResourceTicket* ticket = ticketIter->second;
482     ticket->Uploaded();
483   }
484 }
485
486 void ResourceClient::NotifySaveRequested( ResourceId id )
487 {
488   DALI_LOG_INFO(Debug::Filter::gResource, Debug::General, "ResourceClient: NotifySaveRequested(id:%u)\n", id);
489
490   TicketContainerIter ticketIter = mImpl->mTickets.find(id);
491   if(ticketIter != mImpl->mTickets.end())
492   {
493     ResourceTicket* ticket = ticketIter->second;
494     SaveResource( ticket, "" );
495   }
496 }
497
498
499 void ResourceClient::NotifyLoading( ResourceId id )
500 {
501   DALI_LOG_INFO(Debug::Filter::gResource, Debug::General, "ResourceClient: NotifyLoading(id:%u)\n", id);
502
503   TicketContainerIter ticketIter = mImpl->mTickets.find(id);
504   if(ticketIter != mImpl->mTickets.end())
505   {
506     ResourceTicket* ticket = ticketIter->second;
507     ticket->Loading();
508   }
509 }
510
511 void ResourceClient::NotifyLoadingSucceeded( ResourceId id )
512 {
513   DALI_LOG_INFO(Debug::Filter::gResource, Debug::General, "ResourceClient: NotifyLoadingSucceeded(id:%u)\n", id);
514
515   TicketContainerIter ticketIter = mImpl->mTickets.find(id);
516   if(ticketIter != mImpl->mTickets.end())
517   {
518     ResourceTicket* ticket = ticketIter->second;
519     ticket->LoadingSucceeded();
520   }
521 }
522
523 void ResourceClient::NotifyLoadingFailed( ResourceId id )
524 {
525   DALI_LOG_INFO(Debug::Filter::gResource, Debug::General, "ResourceClient: NotifyLoadingFailed(id:%u)\n", id);
526
527   TicketContainerIter ticketIter = mImpl->mTickets.find(id);
528   if(ticketIter != mImpl->mTickets.end())
529   {
530     ResourceTicket* ticket = ticketIter->second;
531     ticket->LoadingFailed();
532   }
533 }
534
535 void ResourceClient::NotifySavingSucceeded( ResourceId id )
536 {
537   DALI_LOG_INFO(Debug::Filter::gResource, Debug::General, "ResourceClient: NotifySavingSucceeded(id:%u)\n", id);
538
539   TicketContainerIter ticketIter = mImpl->mTickets.find(id);
540   if(ticketIter != mImpl->mTickets.end())
541   {
542     ResourceTicket* ticket = ticketIter->second;
543     ticket->SavingSucceeded();
544   }
545 }
546
547 void ResourceClient::NotifySavingFailed( ResourceId id )
548 {
549   DALI_LOG_INFO(Debug::Filter::gResource, Debug::General, "ResourceClient: NotifySavingFailed(id:%u)\n", id);
550
551   TicketContainerIter ticketIter = mImpl->mTickets.find(id);
552   if(ticketIter != mImpl->mTickets.end())
553   {
554     ResourceTicket* ticket = ticketIter->second;
555     ticket->SavingFailed();
556   }
557 }
558
559 void ResourceClient::UpdateImageTicket( ResourceId id, const ImageAttributes& imageAttributes ) ///!< Issue #AHC01
560 {
561   DALI_LOG_INFO(Debug::Filter::gResource, Debug::General, "ResourceClient: UpdateImageTicket(id:%u)\n", id);
562
563   TicketContainerIter ticketIter = mImpl->mTickets.find(id);
564   if(ticketIter != mImpl->mTickets.end())
565   {
566     ResourceTicket* ticket = ticketIter->second;
567     ImageTicketPtr imageTicket = dynamic_cast<ImageTicket*>(ticket);
568     if(imageTicket)
569     {
570       imageTicket->mAttributes = imageAttributes;
571     }
572   }
573 }
574
575 } // Internal
576
577 } // Dali