root/src/kits/game/DirectWindow.cpp
/*
 * Copyright 2003-2009 Haiku, Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Stefano Ceccherini <stefano.ceccherini@gmail.com>
 *              Carwyn Jones <turok2@currantbun.com>
 */


#include <DirectWindow.h>

#include <stdio.h>
#include <string.h>

#include <Screen.h>

#include <clipping.h>
#include <AppServerLink.h>
#include <ApplicationPrivate.h>
#include <DirectWindowPrivate.h>
#include <ServerProtocol.h>
#include <ServerMemoryAllocator.h>


//#define DEBUG         1
#define OUTPUT          printf
//#define OUTPUT        debug_printf


// We don't need this kind of locking, since the directDaemonFunc
// doesn't access critical shared data.
#define DW_NEEDS_LOCKING 0

enum dw_status_bits {
        DW_STATUS_AREA_CLONED    = 0x1,
        DW_STATUS_THREAD_STARTED = 0x2,
        DW_STATUS_SEM_CREATED    = 0x4
};


#if DEBUG


static void
print_direct_buffer_state(const direct_buffer_state &state)
{
        char string[128];
        int modeState = state & B_DIRECT_MODE_MASK;
        if (modeState == B_DIRECT_START)
                strcpy(string, "B_DIRECT_START");
        else if (modeState == B_DIRECT_MODIFY)
                strcpy(string, "B_DIRECT_MODIFY");
        else if (modeState == B_DIRECT_STOP)
                strcpy(string, "B_DIRECT_STOP");

        if (state & B_CLIPPING_MODIFIED)
                strcat(string, " | B_CLIPPING_MODIFIED");
        if (state & B_BUFFER_RESIZED)
                strcat(string, " | B_BUFFER_RESIZED");
        if (state & B_BUFFER_MOVED)
                strcat(string, " | B_BUFFER_MOVED");
        if (state & B_BUFFER_RESET)
                strcat(string, " | B_BUFFER_RESET");

        OUTPUT("direct_buffer_state: %s\n", string);
}


static void
print_direct_driver_state(const direct_driver_state &state)
{
        if (state == 0)
                return;

        char string[64];
        if (state == B_DRIVER_CHANGED)
                strcpy(string, "B_DRIVER_CHANGED");
        else if (state == B_MODE_CHANGED)
                strcpy(string, "B_MODE_CHANGED");

        OUTPUT("direct_driver_state: %s\n", string);
}


#if DEBUG > 1


static void
print_direct_buffer_layout(const buffer_layout &layout)
{
        char string[64];
        if (layout == B_BUFFER_NONINTERLEAVED)
                strcpy(string, "B_BUFFER_NONINTERLEAVED");
        else
                strcpy(string, "unknown");

        OUTPUT("layout: %s\n", string);
}


static void
print_direct_buffer_orientation(const buffer_orientation &orientation)
{
        char string[64];
        switch (orientation) {
                case B_BUFFER_TOP_TO_BOTTOM:
                        strcpy(string, "B_BUFFER_TOP_TO_BOTTOM");
                        break;
                case B_BUFFER_BOTTOM_TO_TOP:
                        strcpy(string, "B_BUFFER_BOTTOM_TO_TOP");
                        break;
                default:
                        strcpy(string, "unknown");
                        break;
        }

        OUTPUT("orientation: %s\n", string);
}


#endif  // DEBUG > 2


static void
print_direct_buffer_info(const direct_buffer_info &info)
{
        print_direct_buffer_state(info.buffer_state);
        print_direct_driver_state(info.driver_state);

#       if DEBUG > 1
        OUTPUT("bits: %p\n", info.bits);
        OUTPUT("bytes_per_row: %ld\n", info.bytes_per_row);
        OUTPUT("bits_per_pixel: %lu\n", info.bits_per_pixel);
        OUTPUT("pixel_format: %d\n", info.pixel_format);
        print_direct_buffer_layout(info.layout);
        print_direct_buffer_orientation(info.orientation);

#               if DEBUG > 2
        // TODO: this won't work correctly with debug_printf()
        printf("CLIPPING INFO:\n");
        printf("clipping_rects count: %ld\n", info.clip_list_count);

        printf("- window_bounds:\n");
        BRegion region;
        region.Set(info.window_bounds);
        region.PrintToStream();

        region.MakeEmpty();
        for (uint32 i = 0; i < info.clip_list_count; i++)
                region.Include(info.clip_list[i]);

        printf("- clip_list:\n");
        region.PrintToStream();
#               endif
#       endif

        OUTPUT("\n");
}


#endif  // DEBUG


//      #pragma mark -


BDirectWindow::BDirectWindow(BRect frame, const char* title, window_type type,
                uint32 flags, uint32 workspace)
        :
        BWindow(frame, title, type, flags, workspace)
{
        _InitData();
}


BDirectWindow::BDirectWindow(BRect frame, const char* title, window_look look,
                window_feel feel, uint32 flags, uint32 workspace)
        :
        BWindow(frame, title, look, feel, flags, workspace)
{
        _InitData();
}


BDirectWindow::~BDirectWindow()
{
        _DisposeData();
}


//      #pragma mark - BWindow API implementation


BArchivable*
BDirectWindow::Instantiate(BMessage* data)
{
        return NULL;
}


status_t
BDirectWindow::Archive(BMessage* data, bool deep) const
{
        return inherited::Archive(data, deep);
}


void
BDirectWindow::Quit()
{
        inherited::Quit();
}


void
BDirectWindow::DispatchMessage(BMessage* message, BHandler* handler)
{
        inherited::DispatchMessage(message, handler);
}


void
BDirectWindow::MessageReceived(BMessage* message)
{
        inherited::MessageReceived(message);
}


void
BDirectWindow::FrameMoved(BPoint newPosition)
{
        inherited::FrameMoved(newPosition);
}


void
BDirectWindow::WorkspacesChanged(uint32 oldWorkspaces, uint32 newWorkspaces)
{
        inherited::WorkspacesChanged(oldWorkspaces, newWorkspaces);
}


void
BDirectWindow::WorkspaceActivated(int32 index, bool state)
{
        inherited::WorkspaceActivated(index, state);
}


void
BDirectWindow::FrameResized(float newWidth, float newHeight)
{
        inherited::FrameResized(newWidth, newHeight);
}


void
BDirectWindow::Minimize(bool minimize)
{
        inherited::Minimize(minimize);
}


void
BDirectWindow::Zoom(BPoint recPosition, float recWidth, float recHeight)
{
        inherited::Zoom(recPosition, recWidth, recHeight);
}


void
BDirectWindow::ScreenChanged(BRect screenFrame, color_space depth)
{
        inherited::ScreenChanged(screenFrame, depth);
}


void
BDirectWindow::MenusBeginning()
{
        inherited::MenusBeginning();
}


void
BDirectWindow::MenusEnded()
{
        inherited::MenusEnded();
}


void
BDirectWindow::WindowActivated(bool state)
{
        inherited::WindowActivated(state);
}


void
BDirectWindow::Show()
{
        inherited::Show();
}


void
BDirectWindow::Hide()
{
        inherited::Hide();
}


BHandler*
BDirectWindow::ResolveSpecifier(BMessage* message, int32 index,
        BMessage* specifier, int32 what, const char* property)
{
        return inherited::ResolveSpecifier(message, index, specifier, what,
                property);
}


status_t
BDirectWindow::GetSupportedSuites(BMessage* data)
{
        return inherited::GetSupportedSuites(data);
}


status_t
BDirectWindow::Perform(perform_code d, void* arg)
{
        return inherited::Perform(d, arg);
}


void
BDirectWindow::task_looper()
{
        inherited::task_looper();
}


BMessage*
BDirectWindow::ConvertToMessage(void* raw, int32 code)
{
        return inherited::ConvertToMessage(raw, code);
}


//      #pragma mark - BDirectWindow specific API


void
BDirectWindow::DirectConnected(direct_buffer_info* info)
{
        // implemented in subclasses
}


status_t
BDirectWindow::GetClippingRegion(BRegion* region, BPoint* origin) const
{
        if (region == NULL)
                return B_BAD_VALUE;

        if (IsLocked() || !_LockDirect())
                return B_ERROR;

        if (!fInDirectConnected) {
                _UnlockDirect();
                return B_ERROR;
        }

        // BPoint's coordinates are floats. We can only work
        // with integers._DaemonStarter
        int32 originX, originY;
        if (origin == NULL) {
                originX = 0;
                originY = 0;
        } else {
                originX = (int32)origin->x;
                originY = (int32)origin->y;
        }

#ifndef HAIKU_TARGET_PLATFORM_DANO
        // Since we are friend of BRegion, we can access its private members.
        // Otherwise, we would need to call BRegion::Include(clipping_rect)
        // for every clipping_rect in our clip_list, and that would be much
        // more overkill than this (tested ).
        if (!region->_SetSize(fBufferDesc->clip_list_count)) {
                _UnlockDirect();
                return B_NO_MEMORY;
        }
        region->fCount = fBufferDesc->clip_list_count;
        region->fBounds = region->_ConvertToInternal(fBufferDesc->clip_bounds);
        for (uint32 c = 0; c < fBufferDesc->clip_list_count; c++) {
                region->fData[c] = region->_ConvertToInternal(
                        fBufferDesc->clip_list[c]);
        }

        // adjust bounds by the given origin point
        region->OffsetBy(-originX, -originY);
#endif

        _UnlockDirect();

        return B_OK;

}


status_t
BDirectWindow::SetFullScreen(bool enable)
{
        if (fIsFullScreen == enable)
                return B_OK;

        status_t status = B_ERROR;
        if (Lock()) {
                fLink->StartMessage(AS_DIRECT_WINDOW_SET_FULLSCREEN);
                fLink->Attach<bool>(enable);

                if (fLink->FlushWithReply(status) == B_OK
                        && status == B_OK) {
                        fIsFullScreen = enable;
                }
                Unlock();
        }
        return status;
}


bool
BDirectWindow::IsFullScreen() const
{
        return fIsFullScreen;
}


/*static*/ bool
BDirectWindow::SupportsWindowMode(screen_id id)
{
        display_mode mode;
        status_t status = BScreen(id).GetMode(&mode);
        if (status == B_OK)
                return (mode.flags & B_PARALLEL_ACCESS) != 0;

        return false;
}


//      #pragma mark - Private methods


/*static*/ int32
BDirectWindow::_daemon_thread(void* arg)
{
        return static_cast<BDirectWindow*>(arg)->_DirectDaemon();
}


int32
BDirectWindow::_DirectDaemon()
{
        while (!fDaemonKiller) {
                // This sem is released by the app_server when our
                // clipping region changes, or when our window is moved,
                // resized, etc. etc.
                status_t status;
                do {
                        status = acquire_sem(fDisableSem);
                } while (status == B_INTERRUPTED);

                if (status != B_OK) {
                        //fprintf(stderr, "DirectDaemon: failed to acquire direct sem: %s\n",
                                // strerror(status));
                        return -1;
                }

#if DEBUG
                print_direct_buffer_info(*fBufferDesc);
#endif

                if (_LockDirect()) {
                        if ((fBufferDesc->buffer_state & B_DIRECT_MODE_MASK)
                                        == B_DIRECT_START)
                                fConnectionEnable = true;

                        if (fBufferDesc->bits == NULL) {
                                // (re-)clone the bits area
                                BPrivate::AppServerLink linkLocker;
                                        // protects ServerAllocator
                                BPrivate::ServerMemoryAllocator* allocator
                                        = BApplication::Private::ServerAllocator();
                                allocator->RemoveArea(fSourceBitsArea);
                                area_id localArea;
                                uint8* bits;
                                status_t status = allocator->AddArea(fBufferDesc->bits_area,
                                        localArea, bits, (size_t)-1);
                                if (status != B_OK) {
                                        _UnlockDirect();
                                        return -1;
                                }

                                fBufferDesc->bits = (void*)bits;
                                fSourceBitsArea = fBufferDesc->bits_area;
                        }

                        fInDirectConnected = true;
                        DirectConnected(fBufferDesc);
                        fInDirectConnected = false;

                        if ((fBufferDesc->buffer_state & B_DIRECT_MODE_MASK)
                                        == B_DIRECT_STOP)
                                fConnectionEnable = false;

                        _UnlockDirect();
                }

                // The app_server then waits (with a timeout) on this sem.
                // If we aren't quick enough to release this sem, our app
                // will be terminated by the app_server
                if ((status = release_sem(fDisableSemAck)) != B_OK) {
                        //fprintf(stderr, "DirectDaemon: failed to release sem: %s\n",
                                //strerror(status));
                        return -1;
                }
        }

        return 0;
}


bool
BDirectWindow::_LockDirect() const
{
        // LockDirect() and UnlockDirect() are no-op on BeOS. I tried to call BeOS's
        // version repeatedly, from the same thread and from different threads,
        // nothing happened.
        // They're not needed though, as the direct_daemon_thread doesn't change
        // any shared data. They are probably here for future enhancements.
        status_t status = B_OK;

#if DW_NEEDS_LOCKING
        BDirectWindow* casted = const_cast<BDirectWindow*>(this);

        if (atomic_add(&casted->fDirectLock, 1) > 0) {
                do {
                        status = acquire_sem(casted->fDirectSem);
                } while (status == B_INTERRUPTED);
        }

        if (status == B_OK) {
                casted->fDirectLockOwner = find_thread(NULL);
                casted->fDirectLockCount++;
        }
#endif

        return status == B_OK;
}


void
BDirectWindow::_UnlockDirect() const
{
#if DW_NEEDS_LOCKING
        BDirectWindow* casted = const_cast<BDirectWindow*>(this);

        if (atomic_add(&casted->fDirectLock, -1) > 1)
                release_sem(casted->fDirectSem);

        casted->fDirectLockCount--;
#endif
}


void
BDirectWindow::_InitData()
{
        fConnectionEnable = false;
        fIsFullScreen = false;
        fInDirectConnected = false;

        fInitStatus = 0;

        status_t status = B_ERROR;
        struct direct_window_sync_data syncData;

        fLink->StartMessage(AS_DIRECT_WINDOW_GET_SYNC_DATA);
        if (fLink->FlushWithReply(status) == B_OK && status == B_OK)
                fLink->Read<direct_window_sync_data>(&syncData);

        if (status != B_OK)
                return;

#if DW_NEEDS_LOCKING
        fDirectLock = 0;
        fDirectLockCount = 0;
        fDirectLockOwner = -1;
        fDirectLockStack = NULL;
        fDirectSem = create_sem(0, "direct sem");
        if (fDirectSem > 0)
                fInitStatus |= DW_STATUS_SEM_CREATED;
#endif

        fSourceDirectArea = syncData.area;
        fDisableSem = syncData.disable_sem;
        fDisableSemAck = syncData.disable_sem_ack;

        fClonedDirectArea = clone_area("cloned direct area", (void**)&fBufferDesc,
                B_ANY_ADDRESS, B_READ_AREA | B_WRITE_AREA, fSourceDirectArea);

        fSourceBitsArea = -1;

        if (fClonedDirectArea > 0) {
                fInitStatus |= DW_STATUS_AREA_CLONED;

                fDirectDaemonId = spawn_thread(_daemon_thread, "direct daemon",
                        B_DISPLAY_PRIORITY, this);

                if (fDirectDaemonId > 0) {
                        fDaemonKiller = false;
                        if (resume_thread(fDirectDaemonId) == B_OK)
                                fInitStatus |= DW_STATUS_THREAD_STARTED;
                        else
                                kill_thread(fDirectDaemonId);
                }
        }
}


void
BDirectWindow::_DisposeData()
{
        // wait until the connection terminates: we can't destroy
        // the object until the client receives the B_DIRECT_STOP
        // notification, or bad things will happen
        while (fConnectionEnable)
                snooze(50000);

        _LockDirect();

        if (fInitStatus & DW_STATUS_THREAD_STARTED) {
                fDaemonKiller = true;
                // delete this sem, otherwise the Direct daemon thread
                // will wait forever on it
                delete_sem(fDisableSem);
                status_t retVal;
                wait_for_thread(fDirectDaemonId, &retVal);
        }

#if DW_NEEDS_LOCKING
        if (fInitStatus & DW_STATUS_SEM_CREATED)
                delete_sem(fDirectSem);
#endif

        if (fInitStatus & DW_STATUS_AREA_CLONED)
                delete_area(fClonedDirectArea);
}


void BDirectWindow::_ReservedDirectWindow1() {}
void BDirectWindow::_ReservedDirectWindow2() {}
void BDirectWindow::_ReservedDirectWindow3() {}
void BDirectWindow::_ReservedDirectWindow4() {}