locked
Trouble with share...

    Question

  • So, I have a share feature in a DirectX app which waits for a frame to finish rendering and then captures the frame buffer. It works great in the debugger in all build configurations, but once out of the debugger I get a "App can't share right now" message in the share interface. It seems to be deciding this whilst the share code is running - sticking debug spam into it I can see that all of the code gets called and executes without any errors - but before it is finished Windows decides it can't share.

    Now, if I remove the screenshot part of the share - everything works fine in and out of the debugger, so I suspect it might be something to do with waiting for the frame. Is there some kind of time-limit on share that is only enforced outside of the debugger?

    Has anyone else had similar problems and been able to confirm the cause or find solutions?

    Thanks in advance.

    Friday, June 8, 2012 4:09 PM

Answers

  • I was unable to repro any problem using the same class layout you demonstrated above. I didn't implement all the graphics manipulation, so I suspect you are hitting the time limit as the message "App can't share right now. Try again later." equates to a timeout. You may need to use SetDataProvider as mentioned in the GetDeferral doc.

    David Lamb

    • Marked as answer by Semi Essessi Friday, July 27, 2012 10:47 AM
    Thursday, June 14, 2012 11:51 PM
    Moderator

All replies

  • Are you making async calls while handling OnDataRequested? The handler itself will return before the tasks complete and fire off the ::then continuations. By default the share charm will wait only until the handler returns. If the data isn't available it doesn't know that it needs to wait unless you tell it otherwise.

    To make asynchronous calls in the DataRequested handler you'll need to request a deferral by calling DataRequest->GetDeferral on the DataRequest passed in the DataRequestedEventArgs. Once the data is ready, call the deferral's Complete() function to let the share charm know that you're done and the data is ready.

    See How to produce requested data asynchronously for more information.

    --Rob

    Friday, June 8, 2012 6:55 PM
    Owner
  • This is what I am doing already - I have the deferral stored by holding onto a reference to it whilst the threaded task runs, with it being set complete at the end, but share seems to fail before it even gets that far.
    Sunday, June 10, 2012 10:34 PM
  • How long is it taking? There is a time limit on the deferral. I don't remember offhand how long it is, but remember that the user is waiting on this so it cannot take too long.

    --Rob

    Monday, June 11, 2012 2:20 AM
    Owner
  • probably somewhere in the region of 1-100ms depending on the machine. I am doubtful that a time limit is causing the problem since the operation is fast enough to be inperceptible, although if it was very strictly upheld and a low limit, e.g. 20ms then I could believe it to be the problem. I wonder if something to do with reference counting and how I store the deferral is causing the problem...

    It decides very quickly too that it is unable to share - faster than I've seen the spinner waiting on share data.

    the things that makes me suspicious of this most though are that it works fine in the debugger, and it worked fine in the past before CPU2 or RP (I do not especially remember it ever working on either, but it could have). from this I would guess that I am not initialising some data somewhere, or an API has changed that I have not noticed, or is accepting what has now become an invalid parameter thanks to C++ automatic casting.

    I will share some code to show how I am doing this. I apologise for not using the forum built-in code stuff - I am yet to see it render correctly in a browser, and having everything on a single line, with extra spacing makes it difficult to read, or copy-paste (including in IE 9 and 10). I have an instanced class representing the share functionality, which has reference counted pointers to the deferral and some other bits (not holding a reference explicitly gets them destroyed, despite their being passed into various APIs, which I consider a bug - and somewhat defeating the claimed benefits of a reference counting scheme). Please ignore the unfinished, disabled watermarking functionality:

    #ifndef SHARE_H
    #define SHARE_H
    
    namespace StarChart
    {
    
    class D3DRenderer;
    class LocalisationManager;
    
    ref class Share
    {
    internal:
        
        Share( D3DRenderer& renderer, LocalisationManager* localisation );
        ~Share();
    
    private:
    
        void onDataRequested( Windows::ApplicationModel::DataTransfer::DataTransferManager^ sender, Windows::ApplicationModel::DataTransfer::DataRequestedEventArgs^ args );
        void onBitmapEncoderCreated( Windows::Foundation::IAsyncOperation< Windows::Graphics::Imaging::BitmapEncoder^ >^ asyncInfo, Windows::Foundation::AsyncStatus asyncStatus );
        void onWatermarkDecoderCreated( Windows::Foundation::IAsyncOperation< Windows::Graphics::Imaging::BitmapDecoder^ >^ asyncInfo, Windows::Foundation::AsyncStatus asyncStatus );
    
        D3DRenderer& m_renderer;
        LocalisationManager* m_localisation;
        //char* m_watermarkData;
        //volatile int m_watermarkWidth;
        //volatile int m_watermarkHeight;
    
        // SE: needed for pointless threadedness
        Windows::ApplicationModel::DataTransfer::DataRequestDeferral^ m_shareDeferral;
        Windows::ApplicationModel::DataTransfer::DataRequest^ m_shareRequest;
        Windows::Storage::Streams::InMemoryRandomAccessStream^ m_stream;
    };
    
    }
    
    #endif
    
    

    #include "pch.h"
    
    #include "Share.h"
    
    #include "Render\D3DRenderer.h"
    #include "Utilities\AsyncUtils.h"
    #include "Utilities\BasicLoader.h"
    #include "Utilities\BasicReaderWriter.h"
    #include "Utilities\Performance.h"
    
    #include "UI\LocalisationManager.h"
    
    #define DEBUG_SHARE ( 0 )
    #define DEBUG_SHARE_SCREENSHOT ( 0 )
    
    #if DEBUG_SHARE
    #define ShareMessage( format, ... ) Debug::onScreenMessage( "[SHARE] " format, __VA_ARGS__ )
    #else
    #define ShareMessage( ... )
    #endif
    
    using namespace Windows::ApplicationModel::DataTransfer;
    using namespace Windows::Foundation;
    using namespace Windows::Graphics::Imaging;
    using namespace Windows::Storage;
    using namespace Windows::Storage::Streams;
    using namespace Windows::UI::Core;
    
    namespace StarChart
    {
    
    Share::Share( D3DRenderer& renderer, LocalisationManager* localisation )
    : m_renderer( renderer )
    , m_localisation(localisation)
    //, m_watermarkData( 0 )
    //, m_watermarkWidth( 0 )
    //, m_watermarkHeight( 0 )
    , m_shareDeferral( nullptr )
    , m_shareRequest( nullptr )
    , m_stream( nullptr )
    {
        DataTransferManager^ shareManager = DataTransferManager::GetForCurrentView();
    
    #if !REMOVE_SHARE
        ShareMessage( "Registered event handler for share" );
        shareManager->DataRequested += ref new TypedEventHandler< DataTransferManager^, DataRequestedEventArgs^ >( this, &Share::onDataRequested );
    #endif
    
        //// setup watermark source data...
        //BasicLoader^ loader = m_renderer.loader();
        //BasicReaderWriter^ readWriter = loader->basicReaderWriter();
    
        //Platform::Array< byte >^ byteArray = readWriter->ReadData( ref new Platform::String( L"../../Assets/starchart_watermark.png" ) );
    
        //// SE - TODO: work out wtf hoops to jump through to use this pos api
        //BitmapDecoder::CreateAsync( BitmapDecoder::PngDecoderId, someDifficultToUseStreamThingy )->Completed
        // = ref new AsyncOperationCompletedHandler< BitmapDecoder^ >( this, &Share::onWatermarkDecoderCreated ); 
    }
    
    Share::~Share()
    {
        //delete[] m_watermarkData;
    }
    
    void Share::onDataRequested( DataTransferManager^ sender, DataRequestedEventArgs^ args )
    {
        ShareMessage( "onDataRequested called by Windows" );
        m_shareRequest = args->Request;
        m_shareDeferral = m_shareRequest->GetDeferral();
    
        ShareMessage( "Kicking frame capture request" );
    
        // NOTE: kick 'frame capture task'
        m_renderer.requestFrameCapture();
    
    #if REMOVE_SHARE_SCREENSHOT
        DataPackage^ data = m_shareRequest->Data;
        DataPackagePropertySet^ properties = data->Properties;
    
        properties->Title = m_localisation->getString(L"Share");
        properties->Description = m_localisation->getString(L"Share_desc");
        data->SetText( m_localisation->getString(L"Share_text"));
    
        m_shareDeferral->Complete();
    
        m_shareDeferral = nullptr;
        m_shareRequest = nullptr;
    #else
        // SE - TODO: recycle this? don't trust refcounting to delete one bit...
        m_stream = ref new InMemoryRandomAccessStream();
        BitmapEncoder::CreateAsync( BitmapEncoder::PngEncoderId, m_stream )->Completed
            = ref new AsyncOperationCompletedHandler< BitmapEncoder^ >( this, &Share::onBitmapEncoderCreated );
    #endif
    }
    
    void Share::onBitmapEncoderCreated( IAsyncOperation< Windows::Graphics::Imaging::BitmapEncoder^ >^ asyncInfo, AsyncStatus asyncStatus )
    {
        ShareMessage( "Created bitmap encoder successfully" );
    
        // NOTE: spin-lock waiting on 'frame capture task'
        while( !m_renderer.captureComplete() );
    
        ShareMessage( "Frame capture completed" );
    
        if( asyncInfo->Status == AsyncStatus::Completed )
        {
            Platform::Array< unsigned char >^ bytes =
                ref new Platform::Array< unsigned char >(
                    static_cast< unsigned char* >( m_renderer.getFrameCaptureData() ), m_renderer.getFrameCaptureSize() );
        
            BitmapEncoder^ bitmapEncoder = asyncInfo->GetResults();
    
            //// NOTE: spin-lock waiting on 'watermark data' - very unlikely to spin
            //while( !( m_watermarkWidth && m_watermarkHeight ) );
    
    
            bitmapEncoder->SetPixelData(
                BitmapPixelFormat::Bgra8,
                BitmapAlphaMode::Ignore,
                m_renderer.getFrameCaptureWidth(),
                m_renderer.getFrameCaptureHeight(),
                96.0, 96.0, bytes );
    
            ShareMessage( "Writing frame capture data to stream" );
    
            CompleteOperation( bitmapEncoder->FlushAsync() );
    
            ShareMessage( "Flushed stream successfully" );
    
            DataPackage^ data = m_shareRequest->Data;
            DataPackagePropertySet^ properties = data->Properties;
    
            properties->Title = m_localisation->getString(L"Share");
            properties->Description = m_localisation->getString(L"Share_desc");
            data->SetText( m_localisation->getString(L"Share_text"));
            data->SetBitmap( RandomAccessStreamReference::CreateFromStream( m_stream ) );
    
            ShareMessage( "Provided share with data" );
    
    #if DEBUG_SHARE_SCREENSHOT
    
            StorageFile^ file = CompleteOperation(
                ApplicationData::Current->LocalFolder->CreateFileAsync(
                    ref new Platform::String( L"ShareCapture.png" ), CreationCollisionOption::ReplaceExisting ) );
    
            StorageStreamTransaction^ transaction = CompleteOperation( file->OpenTransactedWriteAsync() );
            
            BitmapEncoder^ fileStreamEncoder =
                CompleteOperation( BitmapEncoder::CreateAsync( BitmapEncoder::PngEncoderId, transaction->Stream ) );
            fileStreamEncoder->SetPixelData(
                BitmapPixelFormat::Bgra8,
                BitmapAlphaMode::Ignore,
                m_renderer.getFrameCaptureWidth(),
                m_renderer.getFrameCaptureHeight(),
                96.0, 96.0, bytes );
            CompleteOperation( fileStreamEncoder->FlushAsync() );
    
            transaction->CommitAsync();
    #endif
        }
    
        m_shareDeferral->Complete();
    
        ShareMessage( "Share completed" );
    
        m_shareDeferral = nullptr;
        m_shareRequest = nullptr;
    }
    
    void Share::onWatermarkDecoderCreated( IAsyncOperation< Windows::Graphics::Imaging::BitmapDecoder^ >^ asyncInfo, AsyncStatus asyncStatus )
    {
    // if( asyncInfo->Status == AsyncStatus::Completed )
    // {
    // BitmapDecoder^ bitmapDecoder = asyncInfo->GetResults();
    // PixelDataProvider^ dataProvider = CompleteOperation( bitmapDecoder->GetPixelDataAsync() );
    //
    // Platform::Array<byte>^ byteArray = dataProvider->DetachPixelData();
    // int size = m_watermarkHeight * m_watermarkWidth;
    // m_watermarkData = new char[ m_watermarkHeight * m_watermarkWidth ];
    // for( int i = 0; i < size; ++i )
    // {
    // m_watermarkData[ i ] = byteArray[ i * 4 + 2 ];
    // }
    //
    // m_watermarkHeight = bitmapDecoder->PixelHeight;
    // m_watermarkWidth = bitmapDecoder->PixelWidth;
    // }
    // else
    // {
    // // SE: we are royally screwed if we end up here - things will crash!
    //#ifndef MASTER
    // __asm int 3
    //#endif
    // }
    }
    
    }
    

    Monday, June 11, 2012 3:18 PM
  • No more suggestions from anyone?

    It seems to me that there is some undocumented criteria or bug allowing windows to believe that the app is incapable of sharing.

    Wednesday, June 13, 2012 5:31 PM
  • I'll look into this this evening. I'll update you on any developments.

    David Lamb

    Wednesday, June 13, 2012 11:10 PM
    Moderator
  • I was unable to repro any problem using the same class layout you demonstrated above. I didn't implement all the graphics manipulation, so I suspect you are hitting the time limit as the message "App can't share right now. Try again later." equates to a timeout. You may need to use SetDataProvider as mentioned in the GetDeferral doc.

    David Lamb

    • Marked as answer by Semi Essessi Friday, July 27, 2012 10:47 AM
    Thursday, June 14, 2012 11:51 PM
    Moderator
  • Thanks for investigating this, it is appreciated.

    Do we know what this time limit is? If its small enough to be the cause of this problem then I'd strongly recommend increasing it... I have this problem even if the entire share operation completes in less than 20ms.

    At any rate, having different behaviour when run in the debugger is a bit of a gotcha - although it is good practice to test with the product that will actually ship outside of the debugger and on the target OS version the problem here seems to be partly down to the behaviour of the OS changing between updates. This worked fine in CP when I first tested it, and unfortunately it was never tested with CPU2, which is our mistake. However, because RP was released after the app was submitted if the change was made between CPU2 and RP there really would have been no way for us to avoid this problem.

    I will look into using SetDataProvider - it does sound like the right approach in this case, regardless of time limits etc...

    Friday, June 15, 2012 1:50 PM
  • The doc's state 200ms, so if you have a sample project that indicates a much shorter interval used I'll look into it further. You can email it to me a DavidLam at Microsoft Dot Com.


    David Lamb

    Friday, June 15, 2012 4:04 PM
    Moderator
  • Just wanted to say that I encounter the same problem. I'm am also rendering a bitmap and create an image file from it during the sharing process. Just like in your case it works fine in the debugger but once deployed it fails about 90% of the time on the tablet and about 20% of the time on my (fast) laptop, so it looks like it's a timeout.
    Wednesday, June 27, 2012 12:36 PM
  • FYI, using the setdataprovider functionality worked fine. :)

    thanks

    Friday, July 27, 2012 10:47 AM
  • Semi Essessi, does your solution work with standard metro mail cilent?
    Monday, September 17, 2012 11:15 AM