2020-09-14 16:13:36 +02:00
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
2021-04-16 17:05:24 +02:00
// Copyright © 2005 The Android Open Source Project
// Copyright © 2019-2020 Ryujinx Team and Contributors
2020-09-14 16:13:36 +02:00
# include <gpu.h>
2021-05-22 19:46:28 +02:00
# include <gpu/texture/format.h>
2021-04-16 17:05:24 +02:00
# include <soc.h>
2020-10-28 17:00:39 +01:00
# include <services/nvdrv/devices/nvmap.h>
2020-09-14 16:13:36 +02:00
# include <services/common/fence.h>
# include "GraphicBufferProducer.h"
namespace skyline : : service : : hosbinder {
2021-07-17 18:44:48 +02:00
GraphicBufferProducer : : GraphicBufferProducer ( const DeviceState & state , nvdrv : : core : : NvMap & nvMap ) : state ( state ) , bufferEvent ( std : : make_shared < kernel : : type : : KEvent > ( state , true ) ) , nvMap ( nvMap ) { }
void GraphicBufferProducer : : FreeGraphicBufferNvMap ( GraphicBuffer & buffer ) {
auto surface { buffer . graphicHandle . surfaces . at ( 0 ) } ;
u32 nvMapHandleId { surface . nvmapHandle ? surface . nvmapHandle : buffer . graphicHandle . nvmapId } ;
nvMap . FreeHandle ( nvMapHandleId , true ) ;
}
2020-09-14 16:13:36 +02:00
2021-07-10 15:38:18 +02:00
u32 GraphicBufferProducer : : GetPendingBufferCount ( ) {
u32 count { } ;
2021-04-16 17:05:24 +02:00
for ( auto it { queue . begin ( ) } , end { it + activeSlotCount } ; it < end ; it + + )
if ( it - > state = = BufferState : : Queued )
count + + ;
return count ;
}
AndroidStatus GraphicBufferProducer : : RequestBuffer ( i32 slot , GraphicBuffer * & buffer ) {
2021-07-04 06:09:53 +02:00
std : : scoped_lock lock ( mutex ) ;
2021-04-16 17:05:24 +02:00
if ( slot < 0 | | slot > = queue . size ( ) ) [[unlikely]] {
state . logger - > Warn ( " #{} was out of range " , slot ) ;
return AndroidStatus : : BadValue ;
}
2020-09-14 16:13:36 +02:00
2021-04-16 17:05:24 +02:00
auto & bufferSlot { queue [ slot ] } ;
bufferSlot . wasBufferRequested = true ;
buffer = bufferSlot . graphicBuffer . get ( ) ;
2020-09-14 16:13:36 +02:00
2021-04-16 17:05:24 +02:00
state . logger - > Debug ( " #{} " , slot ) ;
return AndroidStatus : : Ok ;
2020-09-14 16:13:36 +02:00
}
2021-07-04 06:09:53 +02:00
AndroidStatus GraphicBufferProducer : : SetBufferCount ( i32 count ) {
std : : scoped_lock lock ( mutex ) ;
if ( count > = MaxSlotCount ) [[unlikely]] {
state . logger - > Warn ( " Setting buffer count too high: {} (Max: {}) " , count , MaxSlotCount ) ;
return AndroidStatus : : BadValue ;
}
for ( auto it { queue . begin ( ) } ; it ! = queue . end ( ) ; it + + ) {
if ( it - > state = = BufferState : : Dequeued ) {
state . logger - > Warn ( " Cannot set buffer count as #{} is dequeued " , std : : distance ( queue . begin ( ) , it ) ) ;
return AndroidStatus : : BadValue ;
}
}
if ( ! count ) {
activeSlotCount = 0 ;
bufferEvent - > Signal ( ) ;
return AndroidStatus : : Ok ;
}
// We don't check minBufferSlots here since it's effectively hardcoded to 0 on HOS (See NativeWindowQuery::MinUndequeuedBuffers)
// HOS only resets all the buffers if there's no preallocated buffers, it simply sets the active buffer count otherwise
if ( preallocatedBufferCount = = 0 ) {
for ( auto & slot : queue ) {
slot . state = BufferState : : Free ;
slot . frameNumber = std : : numeric_limits < u32 > : : max ( ) ;
2021-07-17 18:44:48 +02:00
if ( slot . texture ) {
slot . texture = { } ;
FreeGraphicBufferNvMap ( * slot . graphicBuffer ) ;
}
2021-07-04 06:09:53 +02:00
slot . graphicBuffer = nullptr ;
}
} else if ( preallocatedBufferCount < count ) {
state . logger - > Warn ( " Setting the active slot count ({}) higher than the amount of slots with preallocated buffers ({}) " , count , preallocatedBufferCount ) ;
}
activeSlotCount = count ;
bufferEvent - > Signal ( ) ;
return AndroidStatus : : Ok ;
}
2021-04-16 17:05:24 +02:00
AndroidStatus GraphicBufferProducer : : DequeueBuffer ( bool async , u32 width , u32 height , AndroidPixelFormat format , u32 usage , i32 & slot , std : : optional < AndroidFence > & fence ) {
if ( ( width & & ! height ) | | ( ! width & & height ) ) {
state . logger - > Warn ( " Dimensions {}x{} should be uniformly zero or non-zero " , width , height ) ;
return AndroidStatus : : BadValue ;
}
2021-06-18 12:55:19 +02:00
constexpr i32 InvalidGraphicBufferSlot { - 1 } ; //!< https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferQueueCore.h;l=61
slot = InvalidGraphicBufferSlot ;
2021-04-16 17:05:24 +02:00
2021-07-04 06:09:53 +02:00
std : : scoped_lock lock ( mutex ) ;
// We don't need a loop here since the consumer is blocking and instantly frees all buffers
2021-08-20 21:57:22 +02:00
// If a valid slot is not found on the first iteration then it would be stuck in an infinite loop
2021-07-04 06:09:53 +02:00
// As a result of this, we simply warn and return InvalidOperation to the guest
2021-06-18 12:55:19 +02:00
auto buffer { queue . end ( ) } ;
2021-06-18 21:48:29 +02:00
size_t dequeuedSlotCount { } ;
2021-07-04 06:09:53 +02:00
for ( auto it { queue . begin ( ) } ; it ! = std : : min ( queue . begin ( ) + activeSlotCount , queue . end ( ) ) ; it + + ) {
2021-06-18 21:48:29 +02:00
// We want to select the oldest slot that's free to use as we'd want all slots to be used
// If we go linearly then we have a higher preference for selecting the former slots and being out of order
2021-07-04 06:09:53 +02:00
if ( it - > state = = BufferState : : Free ) {
2021-06-18 21:48:29 +02:00
if ( buffer = = queue . end ( ) | | it - > frameNumber < buffer - > frameNumber )
buffer = it ;
} else if ( it - > state = = BufferState : : Dequeued ) {
dequeuedSlotCount + + ;
2021-06-18 12:55:19 +02:00
}
2021-06-18 21:48:29 +02:00
}
2021-06-18 12:55:19 +02:00
2021-06-18 21:48:29 +02:00
if ( buffer ! = queue . end ( ) ) {
2021-08-20 21:57:22 +02:00
slot = static_cast < i32 > ( std : : distance ( queue . begin ( ) , buffer ) ) ;
2021-06-18 21:48:29 +02:00
} else if ( async ) {
return AndroidStatus : : WouldBlock ;
} else if ( dequeuedSlotCount = = queue . size ( ) ) {
state . logger - > Warn ( " Client attempting to dequeue more buffers when all buffers are dequeued by the client: {} " , dequeuedSlotCount ) ;
return AndroidStatus : : InvalidOperation ;
} else {
size_t index { } ;
std : : string bufferString ;
2021-07-04 01:51:19 +02:00
for ( auto & bufferSlot : queue )
2021-07-10 15:38:18 +02:00
bufferString + = util : : Format ( " \n #{} - State: {}, Has Graphic Buffer: {}, Frame Number: {} " , + + index , ToString ( bufferSlot . state ) , bufferSlot . graphicBuffer ! = nullptr , bufferSlot . frameNumber ) ;
2021-06-18 21:48:29 +02:00
state . logger - > Warn ( " Cannot find any free buffers to dequeue:{} " , bufferString ) ;
return AndroidStatus : : InvalidOperation ;
2021-04-16 17:05:24 +02:00
}
width = width ? width : defaultWidth ;
height = height ? height : defaultHeight ;
format = ( format ! = AndroidPixelFormat : : None ) ? format : defaultFormat ;
2020-09-14 16:13:36 +02:00
2021-06-18 12:55:19 +02:00
if ( ! buffer - > graphicBuffer ) {
2021-04-16 17:05:24 +02:00
// Horizon OS doesn't ever allocate memory for the buffers on the GraphicBufferProducer end
// All buffers must be preallocated on the client application and attached to an Android buffer using SetPreallocatedBuffer
return AndroidStatus : : NoMemory ;
}
2021-07-23 04:38:47 +02:00
auto & handle { buffer - > graphicBuffer - > graphicHandle } ;
auto & surface { handle . surfaces . front ( ) } ;
if ( handle . format ! = format | | surface . width ! = width | | surface . height ! = height | | ( buffer - > graphicBuffer - > usage & usage ) ! = usage ) {
2021-06-18 12:55:19 +02:00
state . logger - > Warn ( " Buffer which has been dequeued isn't compatible with the supplied parameters: Dimensions: {}x{}={}x{}, Format: {}={}, Usage: 0x{:X}=0x{:X} " , width , height , surface . width , surface . height , ToString ( format ) , ToString ( buffer - > graphicBuffer - > format ) , usage , buffer - > graphicBuffer - > usage ) ;
2021-04-16 17:05:24 +02:00
// Nintendo doesn't deallocate the slot which was picked in here and reallocate it as a compatible buffer
// This is related to the comment above, Nintendo only allocates buffers on the client side
return AndroidStatus : : NoInit ;
2020-09-14 16:13:36 +02:00
}
2021-06-18 12:55:19 +02:00
buffer - > state = BufferState : : Dequeued ;
2021-04-16 17:05:24 +02:00
fence = AndroidFence { } ; // We just let the presentation engine return a buffer which is ready to be written into, there is no need for further synchronization
2020-09-14 16:13:36 +02:00
2021-04-16 17:05:24 +02:00
state . logger - > Debug ( " #{} - Dimensions: {}x{}, Format: {}, Usage: 0x{:X}, Is Async: {} " , slot , width , height , ToString ( format ) , usage , async ) ;
return AndroidStatus : : Ok ;
2020-09-14 16:13:36 +02:00
}
2021-07-04 06:09:53 +02:00
AndroidStatus GraphicBufferProducer : : DetachBuffer ( i32 slot ) {
std : : scoped_lock lock ( mutex ) ;
if ( slot < 0 | | slot > = queue . size ( ) ) [[unlikely]] {
state . logger - > Warn ( " #{} was out of range " , slot ) ;
return AndroidStatus : : BadValue ;
}
auto & bufferSlot { queue [ slot ] } ;
if ( bufferSlot . state ! = BufferState : : Dequeued ) [[unlikely]] {
state . logger - > Warn ( " #{} was '{}' instead of being dequeued " , slot , ToString ( bufferSlot . state ) ) ;
return AndroidStatus : : BadValue ;
} else if ( ! bufferSlot . wasBufferRequested ) [[unlikely]] {
state . logger - > Warn ( " #{} was detached prior to being requested " , slot ) ;
return AndroidStatus : : BadValue ;
}
bufferSlot . state = BufferState : : Free ;
bufferSlot . frameNumber = std : : numeric_limits < u32 > : : max ( ) ;
2021-07-17 18:44:48 +02:00
if ( bufferSlot . texture ) {
bufferSlot . texture = { } ;
FreeGraphicBufferNvMap ( * bufferSlot . graphicBuffer ) ;
}
2021-07-04 06:09:53 +02:00
bufferSlot . graphicBuffer = nullptr ;
bufferEvent - > Signal ( ) ;
state . logger - > Debug ( " #{} " , slot ) ;
return AndroidStatus : : Ok ;
}
AndroidStatus GraphicBufferProducer : : DetachNextBuffer ( std : : optional < GraphicBuffer > & graphicBuffer , std : : optional < AndroidFence > & fence ) {
std : : scoped_lock lock ( mutex ) ;
auto bufferSlot { queue . end ( ) } ;
for ( auto it { queue . begin ( ) } ; it ! = queue . end ( ) ; it + + ) {
if ( it - > state = = BufferState : : Free & & it - > graphicBuffer ) {
if ( bufferSlot = = queue . end ( ) | | it - > frameNumber < bufferSlot - > frameNumber )
bufferSlot = it ;
}
}
if ( bufferSlot = = queue . end ( ) )
return AndroidStatus : : NoMemory ;
bufferSlot - > state = BufferState : : Free ;
bufferSlot - > frameNumber = std : : numeric_limits < u32 > : : max ( ) ;
2021-07-17 18:44:48 +02:00
if ( bufferSlot - > texture ) {
bufferSlot - > texture = { } ;
FreeGraphicBufferNvMap ( * bufferSlot - > graphicBuffer ) ;
}
2021-07-04 06:09:53 +02:00
graphicBuffer = * std : : exchange ( bufferSlot - > graphicBuffer , nullptr ) ;
fence = AndroidFence { } ;
bufferEvent - > Signal ( ) ;
state . logger - > Debug ( " #{} " , std : : distance ( queue . begin ( ) , bufferSlot ) ) ;
return AndroidStatus : : Ok ;
}
AndroidStatus GraphicBufferProducer : : AttachBuffer ( i32 & slot , const GraphicBuffer & graphicBuffer ) {
std : : scoped_lock lock ( mutex ) ;
auto bufferSlot { queue . end ( ) } ;
for ( auto it { queue . begin ( ) } ; it ! = queue . end ( ) ; it + + ) {
if ( it - > state = = BufferState : : Free ) {
if ( bufferSlot = = queue . end ( ) | | it - > frameNumber < bufferSlot - > frameNumber )
bufferSlot = it ;
}
}
2021-07-17 18:44:48 +02:00
if ( bufferSlot - > texture ) {
bufferSlot - > texture = { } ;
FreeGraphicBufferNvMap ( * bufferSlot - > graphicBuffer ) ;
}
2021-07-04 06:09:53 +02:00
if ( bufferSlot = = queue . end ( ) ) {
state . logger - > Warn ( " Could not find any free slots to attach the graphic buffer to " ) ;
return AndroidStatus : : NoMemory ;
}
if ( graphicBuffer . magic ! = GraphicBuffer : : Magic )
throw exception ( " Unexpected GraphicBuffer magic: 0x{} (Expected: 0x{}) " , graphicBuffer.magic, GraphicBuffer::Magic) ;
else if ( graphicBuffer . intCount ! = sizeof ( NvGraphicHandle ) / sizeof ( u32 ) )
2021-07-10 15:38:18 +02:00
throw exception ( " Unexpected GraphicBuffer native_handle integer count: 0x{} (Expected: 0x{}) " , graphicBuffer.intCount, sizeof(NvGraphicHandle) / sizeof(u32)) ;
2021-07-04 06:09:53 +02:00
auto & handle { graphicBuffer . graphicHandle } ;
if ( handle . magic ! = NvGraphicHandle : : Magic )
2021-07-10 15:38:18 +02:00
throw exception ( " Unexpected NvGraphicHandle magic: {} " , handle . magic ) ;
2021-07-04 06:09:53 +02:00
else if ( handle . surfaceCount < 1 )
throw exception ( " At least one surface is required in a buffer: {} " , handle . surfaceCount ) ;
else if ( handle . surfaceCount > 1 )
throw exception ( " Multi-planar surfaces are not supported: {} " , handle . surfaceCount ) ;
auto & surface { graphicBuffer . graphicHandle . surfaces . at ( 0 ) } ;
if ( surface . scanFormat ! = NvDisplayScanFormat : : Progressive )
throw exception ( " Non-Progressive surfaces are not supported: {} " , ToString ( surface . scanFormat ) ) ;
else if ( surface . layout = = NvSurfaceLayout : : Tiled )
throw exception ( " Legacy 16Bx16 tiled surfaces are not supported " ) ;
bufferSlot - > state = BufferState : : Dequeued ;
bufferSlot - > wasBufferRequested = true ;
bufferSlot - > isPreallocated = false ;
bufferSlot - > graphicBuffer = std : : make_unique < GraphicBuffer > ( graphicBuffer ) ;
slot = std : : distance ( queue . begin ( ) , bufferSlot ) ;
preallocatedBufferCount = std : : count_if ( queue . begin ( ) , queue . end ( ) , [ ] ( const BufferSlot & slot ) { return slot . graphicBuffer & & slot . isPreallocated ; } ) ;
2021-07-10 15:38:18 +02:00
activeSlotCount = std : : count_if ( queue . begin ( ) , queue . end ( ) , [ ] ( const BufferSlot & slot ) { return slot . graphicBuffer ! = nullptr ; } ) ;
2021-07-04 06:09:53 +02:00
2021-07-23 04:38:47 +02:00
state . logger - > Debug ( " #{} - Dimensions: {}x{} [Stride: {}], Format: {}, Layout: {}, {}: {}, Usage: 0x{:X}, NvMap {}: {}, Buffer Start/End: 0x{:X} -> 0x{:X} " , slot , surface . width , surface . height , handle . stride , ToString ( handle . format ) , ToString ( surface . layout ) , surface . layout = = NvSurfaceLayout : : Blocklinear ? " Block Height " : " Pitch " , surface . layout = = NvSurfaceLayout : : Blocklinear ? 1U < < surface . blockHeightLog2 : surface . pitch , graphicBuffer . usage , surface . nvmapHandle ? " Handle " : " ID " , surface . nvmapHandle ? surface . nvmapHandle : handle . nvmapId , surface . offset , surface . offset + surface . size ) ;
2021-07-04 06:09:53 +02:00
return AndroidStatus : : Ok ;
}
2021-04-16 17:05:24 +02:00
AndroidStatus GraphicBufferProducer : : QueueBuffer ( i32 slot , i64 timestamp , bool isAutoTimestamp , AndroidRect crop , NativeWindowScalingMode scalingMode , NativeWindowTransform transform , NativeWindowTransform stickyTransform , bool async , u32 swapInterval , const AndroidFence & fence , u32 & width , u32 & height , NativeWindowTransform & transformHint , u32 & pendingBufferCount ) {
switch ( scalingMode ) {
case NativeWindowScalingMode : : Freeze :
case NativeWindowScalingMode : : ScaleToWindow :
case NativeWindowScalingMode : : ScaleCrop :
case NativeWindowScalingMode : : NoScaleCrop :
break ;
default :
state . logger - > Warn ( " {} is not a valid scaling mode " , static_cast < u32 > ( scalingMode ) ) ;
return AndroidStatus : : BadValue ;
}
2021-07-04 06:09:53 +02:00
std : : scoped_lock lock ( mutex ) ;
2021-04-16 17:05:24 +02:00
if ( slot < 0 | | slot > = queue . size ( ) ) [[unlikely]] {
state . logger - > Warn ( " #{} was out of range " , slot ) ;
return AndroidStatus : : BadValue ;
}
auto & buffer { queue [ slot ] } ;
if ( buffer . state ! = BufferState : : Dequeued ) [[unlikely]] {
state . logger - > Warn ( " #{} was '{}' instead of being dequeued " , slot , ToString ( buffer . state ) ) ;
return AndroidStatus : : BadValue ;
} else if ( ! buffer . wasBufferRequested ) [[unlikely]] {
state . logger - > Warn ( " #{} was queued prior to being requested " , slot ) ;
2021-06-18 12:55:19 +02:00
buffer . wasBufferRequested = true ; // Switch ignores this and doesn't return an error, certain homebrew ends up depending on this behavior
2021-04-16 17:05:24 +02:00
}
auto graphicBuffer { * buffer . graphicBuffer } ;
if ( graphicBuffer . width < ( crop . right - crop . left ) | | graphicBuffer . height < ( crop . bottom - crop . top ) ) [[unlikely]] {
state . logger - > Warn ( " Crop was out of range for surface buffer: ({}-{})x({}-{}) > {}x{} " , crop . left , crop . right , crop . top , crop . bottom , graphicBuffer . width , graphicBuffer . height ) ;
return AndroidStatus : : BadValue ;
}
2021-07-04 06:09:53 +02:00
if ( ! buffer . texture ) [[unlikely]] {
// We lazily create a texture if one isn't present at queue time, this allows us to look up the texture in the texture cache
// If we deterministically know that the texture is written by the CPU then we can allocate a CPU-shared host texture for fast uploads
2021-07-23 04:38:47 +02:00
auto & handle { graphicBuffer . graphicHandle } ;
if ( handle . magic ! = NvGraphicHandle : : Magic )
throw exception ( " Unexpected NvGraphicHandle magic: {} " , handle . surfaceCount ) ;
else if ( handle . surfaceCount < 1 )
throw exception ( " At least one surface is required in a buffer: {} " , handle . surfaceCount ) ;
else if ( handle . surfaceCount > 1 )
throw exception ( " Multi-planar surfaces are not supported: {} " , handle . surfaceCount ) ;
2021-07-04 06:09:53 +02:00
gpu : : texture : : Format format ;
2021-07-23 04:38:47 +02:00
switch ( handle . format ) {
2021-07-04 06:09:53 +02:00
case AndroidPixelFormat : : RGBA8888 :
case AndroidPixelFormat : : RGBX8888 :
format = gpu : : format : : RGBA8888Unorm ;
break ;
case AndroidPixelFormat : : RGB565 :
format = gpu : : format : : RGB565Unorm ;
break ;
default :
2021-07-23 04:38:47 +02:00
throw exception ( " Unknown format in buffer: '{}' ({}) " , ToString(handle.format), static_cast<u32>(handle.format)) ;
2021-07-04 06:09:53 +02:00
}
auto & surface { graphicBuffer . graphicHandle . surfaces . at ( 0 ) } ;
if ( surface . scanFormat ! = NvDisplayScanFormat : : Progressive )
throw exception ( " Non-Progressive surfaces are not supported: {} " , ToString ( surface . scanFormat ) ) ;
2021-07-17 18:44:48 +02:00
// Duplicate the handle so it can't be freed by the guest
auto nvMapHandleObj { nvMap . GetHandle ( surface . nvmapHandle ? surface . nvmapHandle : handle . nvmapId ) } ;
if ( auto err { nvMapHandleObj - > Duplicate ( true ) } ; err ! = PosixResult : : Success )
throw exception ( " Failed to duplicate graphic buffer NvMap handle: {}! " , static_cast < i32 > ( err ) ) ;
2021-07-04 06:09:53 +02:00
2021-07-17 18:44:48 +02:00
if ( surface . size > ( nvMapHandleObj - > origSize - surface . offset ) )
throw exception ( " Surface doesn't fit into NvMap mapping of size 0x{:X} when mapped at 0x{:X} -> 0x{:X} " , nvMapHandleObj - > origSize , surface . offset , surface . offset + surface . size ) ;
2021-07-04 06:09:53 +02:00
gpu : : texture : : TileMode tileMode ;
gpu : : texture : : TileConfig tileConfig { } ;
if ( surface . layout = = NvSurfaceLayout : : Blocklinear ) {
tileMode = gpu : : texture : : TileMode : : Block ;
tileConfig = {
. surfaceWidth = static_cast < u16 > ( surface . width ) ,
. blockHeight = static_cast < u8 > ( 1U < < surface . blockHeightLog2 ) ,
. blockDepth = 1 ,
} ;
} else if ( surface . layout = = NvSurfaceLayout : : Pitch ) {
tileMode = gpu : : texture : : TileMode : : Pitch ;
tileConfig = {
. pitch = surface . pitch ,
} ;
} else if ( surface . layout = = NvSurfaceLayout : : Tiled ) {
throw exception ( " Legacy 16Bx16 tiled surfaces are not supported " ) ;
}
2021-07-17 18:44:48 +02:00
auto guestTexture { std : : make_shared < gpu : : GuestTexture > ( state , nvMapHandleObj - > GetPointer ( ) + surface . offset , gpu : : texture : : Dimensions ( surface . width , surface . height ) , format , tileMode , tileConfig ) } ;
2021-07-04 06:09:53 +02:00
buffer . texture = guestTexture - > CreateTexture ( { } , vk : : ImageTiling : : eLinear ) ;
}
2021-04-16 17:05:24 +02:00
switch ( transform ) {
case NativeWindowTransform : : Identity :
case NativeWindowTransform : : MirrorHorizontal :
case NativeWindowTransform : : MirrorVertical :
case NativeWindowTransform : : Rotate90 :
case NativeWindowTransform : : Rotate180 :
case NativeWindowTransform : : Rotate270 :
case NativeWindowTransform : : MirrorHorizontalRotate90 :
case NativeWindowTransform : : MirrorVerticalRotate90 :
case NativeWindowTransform : : InvertDisplay :
break ;
default :
throw exception ( " Application attempting to perform unknown transformation: {:#b} " , static_cast < u32 > ( transform ) ) ;
}
2021-07-05 20:49:53 +02:00
switch ( stickyTransform ) {
// Note: Sticky transforms are a legacy feature and aren't implemented in HOS nor the Android version it is based on, they are effectively inert
// Certain games will still pass in values for sticky transforms (even if they don't do anything), we should not assert on these and verify their validity
case NativeWindowTransform : : Identity :
case NativeWindowTransform : : MirrorHorizontal :
case NativeWindowTransform : : MirrorVertical :
case NativeWindowTransform : : Rotate90 :
case NativeWindowTransform : : Rotate180 :
case NativeWindowTransform : : Rotate270 :
case NativeWindowTransform : : MirrorHorizontalRotate90 :
case NativeWindowTransform : : MirrorVerticalRotate90 :
case NativeWindowTransform : : InvertDisplay :
break ;
default :
throw exception ( " Application attempting to perform unknown sticky transformation: {:#b} " , static_cast < u32 > ( stickyTransform ) ) ;
}
2021-04-16 17:05:24 +02:00
fence . Wait ( state . soc - > host1x ) ;
2021-05-22 19:46:28 +02:00
{
2021-06-18 12:55:19 +02:00
auto & texture { buffer . texture } ;
std : : scoped_lock textureLock ( * texture ) ;
texture - > SynchronizeHost ( ) ;
2021-06-25 03:33:02 +02:00
u64 frameId ;
state . gpu - > presentation . Present ( texture , isAutoTimestamp ? 0 : timestamp , swapInterval , crop , scalingMode , transform , frameId ) ;
2021-05-22 19:46:28 +02:00
}
2020-09-14 16:13:36 +02:00
2021-06-25 03:33:02 +02:00
buffer . frameNumber = + + frameNumber ;
2021-06-18 12:55:19 +02:00
buffer . state = BufferState : : Free ;
bufferEvent - > Signal ( ) ;
2021-04-16 17:05:24 +02:00
width = defaultWidth ;
height = defaultHeight ;
transformHint = state . gpu - > presentation . GetTransformHint ( ) ;
pendingBufferCount = GetPendingBufferCount ( ) ;
state . logger - > Debug ( " #{} - {}Timestamp: {}, Crop: ({}-{})x({}-{}), Scale Mode: {}, Transform: {} [Sticky: {}], Swap Interval: {}, Is Async: {} " , slot , isAutoTimestamp ? " Auto " : " " , timestamp , crop . left , crop . right , crop . top , crop . bottom , ToString ( scalingMode ) , ToString ( transform ) , ToString ( stickyTransform ) , swapInterval , async ) ;
return AndroidStatus : : Ok ;
2020-09-14 16:13:36 +02:00
}
2021-04-16 17:05:24 +02:00
void GraphicBufferProducer : : CancelBuffer ( i32 slot , const AndroidFence & fence ) {
2021-07-04 06:09:53 +02:00
std : : scoped_lock lock ( mutex ) ;
2021-04-16 17:05:24 +02:00
if ( slot < 0 | | slot > = queue . size ( ) ) [[unlikely]] {
state . logger - > Warn ( " #{} was out of range " , slot ) ;
return ;
}
auto & buffer { queue [ slot ] } ;
if ( buffer . state ! = BufferState : : Dequeued ) [[unlikely]] {
state . logger - > Warn ( " #{} is not owned by the producer as it is '{}' instead of being dequeued " , slot , ToString ( buffer . state ) ) ;
return ;
}
2020-09-14 16:13:36 +02:00
2021-04-16 17:05:24 +02:00
fence . Wait ( state . soc - > host1x ) ;
buffer . state = BufferState : : Free ;
buffer . frameNumber = 0 ;
2021-06-18 12:55:19 +02:00
bufferEvent - > Signal ( ) ;
2020-09-14 16:13:36 +02:00
2021-04-04 13:09:32 +02:00
state . logger - > Debug ( " #{} " , slot ) ;
2020-09-14 16:13:36 +02:00
}
2021-04-16 17:05:24 +02:00
AndroidStatus GraphicBufferProducer : : Query ( NativeWindowQuery query , u32 & out ) {
2021-07-04 06:09:53 +02:00
std : : scoped_lock lock ( mutex ) ;
2021-04-16 17:05:24 +02:00
switch ( query ) {
case NativeWindowQuery : : Width :
out = defaultWidth ;
break ;
2020-09-14 16:13:36 +02:00
2021-04-16 17:05:24 +02:00
case NativeWindowQuery : : Height :
out = defaultHeight ;
break ;
2020-09-14 16:13:36 +02:00
2021-04-16 17:05:24 +02:00
case NativeWindowQuery : : Format :
out = static_cast < u32 > ( defaultFormat ) ;
break ;
2020-09-14 16:13:36 +02:00
2021-04-16 17:05:24 +02:00
case NativeWindowQuery : : MinUndequeuedBuffers :
// Calls into BufferQueueCore::getMinUndequeuedBufferCountLocked, which always returns mMaxAcquiredBufferCount (0) on HOS as UseAsyncBuffer is false due to HOS not using asynchronous buffers (No allocations on the server are supported)
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/BufferQueueCore.cpp;l=133-145
out = 0 ;
break ;
2020-09-14 16:13:36 +02:00
2021-04-16 17:05:24 +02:00
case NativeWindowQuery : : StickyTransform :
out = static_cast < u32 > ( NativeWindowTransform : : Identity ) ; // We don't support any sticky transforms, they're only used by the LEGACY camera mode
break ;
case NativeWindowQuery : : ConsumerRunningBehind :
out = false ; // We have no way of knowing if the consumer is slower than the producer as we are not notified when a buffer has been acquired on the host
break ;
case NativeWindowQuery : : ConsumerUsageBits :
out = 0 ; // HOS layers (Consumers) have no Gralloc usage bits set
break ;
case NativeWindowQuery : : MaxBufferCount : {
// Calls into BufferQueueCore::getMaxBufferCountLocked, which will always return mDefaultMaxBufferCount (2 which is activeBufferCount's initial value) or mOverrideMaxBufferCount (activeBufferCount) as it's set during SetPreallocatedBuffer
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/BufferQueueCore.cpp;l=151-172
out = activeSlotCount ;
break ;
2020-09-14 16:13:36 +02:00
}
2021-04-16 17:05:24 +02:00
default :
state . logger - > Warn ( " Query not supported: {} " , static_cast < u32 > ( query ) ) ;
return AndroidStatus : : BadValue ;
2020-09-14 16:13:36 +02:00
}
2021-04-16 17:05:24 +02:00
state . logger - > Debug ( " {}: {} " , ToString ( query ) , out ) ;
return AndroidStatus : : Ok ;
}
AndroidStatus GraphicBufferProducer : : Connect ( NativeWindowApi api , bool producerControlledByApp , u32 & width , u32 & height , NativeWindowTransform & transformHint , u32 & pendingBufferCount ) {
2021-07-04 06:09:53 +02:00
std : : scoped_lock lock ( mutex ) ;
2021-04-16 17:05:24 +02:00
if ( connectedApi ! = NativeWindowApi : : None ) [[unlikely]] {
state . logger - > Warn ( " Already connected to API '{}' while connection to '{}' is requested " , ToString ( connectedApi ) , ToString ( api ) ) ;
return AndroidStatus : : BadValue ;
}
switch ( api ) {
case NativeWindowApi : : EGL :
case NativeWindowApi : : CPU :
case NativeWindowApi : : Media :
case NativeWindowApi : : Camera :
break ;
default :
state . logger - > Warn ( " Unknown API: {} " , static_cast < u32 > ( api ) ) ;
return AndroidStatus : : BadValue ;
}
connectedApi = api ;
width = defaultWidth ;
height = defaultHeight ;
transformHint = state . gpu - > presentation . GetTransformHint ( ) ;
pendingBufferCount = GetPendingBufferCount ( ) ;
state . logger - > Debug ( " API: {}, Producer Controlled By App: {}, Default Dimensions: {}x{}, Transform Hint: {}, Pending Buffer Count: {} " , ToString ( api ) , producerControlledByApp , width , height , ToString ( transformHint ) , pendingBufferCount ) ;
return AndroidStatus : : Ok ;
}
AndroidStatus GraphicBufferProducer : : Disconnect ( NativeWindowApi api ) {
2021-07-04 06:09:53 +02:00
std : : scoped_lock lock ( mutex ) ;
2021-04-16 17:05:24 +02:00
switch ( api ) {
case NativeWindowApi : : EGL :
case NativeWindowApi : : CPU :
case NativeWindowApi : : Media :
case NativeWindowApi : : Camera :
break ;
default :
state . logger - > Warn ( " Unknown API: {} " , static_cast < u32 > ( api ) ) ;
return AndroidStatus : : BadValue ;
}
if ( api ! = connectedApi ) {
state . logger - > Warn ( " Disconnecting from API '{}' while connected to '{}' " , ToString ( api ) , ToString ( connectedApi ) ) ;
return AndroidStatus : : BadValue ;
}
connectedApi = NativeWindowApi : : None ;
for ( auto & slot : queue ) {
slot . state = BufferState : : Free ;
slot . frameNumber = std : : numeric_limits < u32 > : : max ( ) ;
2021-07-17 18:44:48 +02:00
if ( slot . texture ) {
slot . texture = { } ;
FreeGraphicBufferNvMap ( * slot . graphicBuffer ) ;
}
2021-04-16 17:05:24 +02:00
slot . graphicBuffer = nullptr ;
}
state . logger - > Debug ( " API: {} " , ToString ( api ) ) ;
return AndroidStatus : : Ok ;
}
2021-07-04 06:09:53 +02:00
AndroidStatus GraphicBufferProducer : : SetPreallocatedBuffer ( i32 slot , const GraphicBuffer * graphicBuffer ) {
std : : scoped_lock lock ( mutex ) ;
2021-04-16 17:05:24 +02:00
if ( slot < 0 | | slot > = MaxSlotCount ) [[unlikely]] {
state . logger - > Warn ( " #{} was out of range " , slot ) ;
return AndroidStatus : : BadValue ;
}
auto & buffer { queue [ slot ] } ;
2021-07-17 18:44:48 +02:00
if ( buffer . texture ) {
buffer . texture = { } ;
FreeGraphicBufferNvMap ( * buffer . graphicBuffer ) ;
}
2021-04-16 17:05:24 +02:00
buffer . state = BufferState : : Free ;
buffer . frameNumber = 0 ;
buffer . wasBufferRequested = false ;
2021-07-10 15:38:18 +02:00
buffer . isPreallocated = graphicBuffer ! = nullptr ;
2021-07-04 06:09:53 +02:00
buffer . graphicBuffer = graphicBuffer ? std : : make_unique < GraphicBuffer > ( * graphicBuffer ) : nullptr ;
if ( graphicBuffer ) {
if ( graphicBuffer - > magic ! = GraphicBuffer : : Magic )
throw exception ( " Unexpected GraphicBuffer magic: 0x{} (Expected: 0x{}) " , graphicBuffer->magic, GraphicBuffer::Magic) ;
else if ( graphicBuffer - > intCount ! = sizeof ( NvGraphicHandle ) / sizeof ( u32 ) )
throw exception ( " Unexpected GraphicBuffer native_handle integer count: 0x{} (Expected: 0x{}) " , graphicBuffer->intCount, sizeof(NvGraphicHandle)) ;
auto & handle { graphicBuffer - > graphicHandle } ;
if ( handle . magic ! = NvGraphicHandle : : Magic )
throw exception ( " Unexpected NvGraphicHandle magic: {} " , handle . surfaceCount ) ;
else if ( handle . surfaceCount < 1 )
throw exception ( " At least one surface is required in a buffer: {} " , handle . surfaceCount ) ;
else if ( handle . surfaceCount > 1 )
throw exception ( " Multi-planar surfaces are not supported: {} " , handle . surfaceCount ) ;
auto & surface { graphicBuffer - > graphicHandle . surfaces . at ( 0 ) } ;
if ( surface . scanFormat ! = NvDisplayScanFormat : : Progressive )
throw exception ( " Non-Progressive surfaces are not supported: {} " , ToString ( surface . scanFormat ) ) ;
else if ( surface . layout = = NvSurfaceLayout : : Tiled )
throw exception ( " Legacy 16Bx16 tiled surfaces are not supported " ) ;
2021-07-23 04:38:47 +02:00
state . logger - > Debug ( " #{} - Dimensions: {}x{} [Stride: {}], Format: {}, Layout: {}, {}: {}, Usage: 0x{:X}, NvMap {}: {}, Buffer Start/End: 0x{:X} -> 0x{:X} " , slot , surface . width , surface . height , handle . stride , ToString ( handle . format ) , ToString ( surface . layout ) , surface . layout = = NvSurfaceLayout : : Blocklinear ? " Block Height " : " Pitch " , surface . layout = = NvSurfaceLayout : : Blocklinear ? 1U < < surface . blockHeightLog2 : surface . pitch , graphicBuffer - > usage , surface . nvmapHandle ? " Handle " : " ID " , surface . nvmapHandle ? surface . nvmapHandle : handle . nvmapId , surface . offset , surface . offset + surface . size ) ;
2021-07-04 06:09:53 +02:00
} else {
state . logger - > Debug ( " #{} - No GraphicBuffer " , slot ) ;
}
2021-04-16 17:05:24 +02:00
2021-07-04 06:09:53 +02:00
preallocatedBufferCount = std : : count_if ( queue . begin ( ) , queue . end ( ) , [ ] ( const BufferSlot & slot ) { return slot . graphicBuffer & & slot . isPreallocated ; } ) ;
2021-07-10 15:38:18 +02:00
activeSlotCount = std : : count_if ( queue . begin ( ) , queue . end ( ) , [ ] ( const BufferSlot & slot ) { return slot . graphicBuffer ! = nullptr ; } ) ;
2020-09-14 16:13:36 +02:00
2021-06-18 12:55:19 +02:00
bufferEvent - > Signal ( ) ;
2020-09-14 16:13:36 +02:00
2021-04-16 17:05:24 +02:00
return AndroidStatus : : Ok ;
2020-09-14 16:13:36 +02:00
}
void GraphicBufferProducer : : OnTransact ( TransactionCode code , Parcel & in , Parcel & out ) {
switch ( code ) {
2021-04-16 17:05:24 +02:00
case TransactionCode : : RequestBuffer : {
GraphicBuffer * buffer { } ;
auto result { RequestBuffer ( in . Pop < i32 > ( ) , buffer ) } ;
out . PushOptionalFlattenable ( buffer ) ;
out . Push ( result ) ;
2020-09-14 16:13:36 +02:00
break ;
2021-04-16 17:05:24 +02:00
}
2021-07-04 06:09:53 +02:00
case TransactionCode : : SetBufferCount : {
auto result { SetBufferCount ( in . Pop < i32 > ( ) ) } ;
out . Push ( result ) ;
break ;
}
2021-04-16 17:05:24 +02:00
case TransactionCode : : DequeueBuffer : {
i32 slot { } ;
std : : optional < AndroidFence > fence { } ;
auto result { DequeueBuffer ( in . Pop < u32 > ( ) , in . Pop < u32 > ( ) , in . Pop < u32 > ( ) , in . Pop < AndroidPixelFormat > ( ) , in . Pop < u32 > ( ) , slot , fence ) } ;
out . Push ( slot ) ;
out . PushOptionalFlattenable ( fence ) ;
out . Push ( result ) ;
2020-09-14 16:13:36 +02:00
break ;
2021-04-16 17:05:24 +02:00
}
2021-07-04 06:09:53 +02:00
case TransactionCode : : DetachBuffer : {
auto result { DetachBuffer ( in . Pop < i32 > ( ) ) } ;
out . Push ( result ) ;
break ;
}
case TransactionCode : : DetachNextBuffer : {
std : : optional < GraphicBuffer > graphicBuffer { } ;
std : : optional < AndroidFence > fence { } ;
auto result { DetachNextBuffer ( graphicBuffer , fence ) } ;
out . PushOptionalFlattenable ( graphicBuffer ) ;
out . PushOptionalFlattenable ( fence ) ;
out . Push ( result ) ;
break ;
}
case TransactionCode : : AttachBuffer : {
i32 slotOut { } ;
auto result { AttachBuffer ( slotOut , in . Pop < GraphicBuffer > ( ) ) } ;
out . Push ( slotOut ) ;
out . Push ( result ) ;
break ;
}
2021-04-16 17:05:24 +02:00
case TransactionCode : : QueueBuffer : {
u32 width { } , height { } , pendingBufferCount { } ;
NativeWindowTransform transformHint { } ;
constexpr u64 QueueBufferInputSize { 0x54 } ; //!< The size of the QueueBufferInput structure (https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/IGraphicBufferProducer.h;l=265-315)
auto slot { in . Pop < i32 > ( ) } ;
auto queueBufferInputSize { in . Pop < u64 > ( ) } ;
if ( queueBufferInputSize ! = QueueBufferInputSize )
throw exception ( " The size of QueueBufferInput in the Parcel (0x{:X}) doesn ' t match the expected size ( 0 x { : X } ) " , queueBufferInputSize, QueueBufferInputSize);
2021-05-22 19:46:28 +02:00
auto result { QueueBuffer ( slot , in . Pop < i64 > ( ) , in . Pop < u32 > ( ) , in . Pop < AndroidRect > ( ) , in . Pop < NativeWindowScalingMode > ( ) , in . Pop < NativeWindowTransform > ( ) , in . Pop < NativeWindowTransform > ( ) , in . Pop < u32 > ( ) , in . Pop < u32 > ( ) , in . Pop < AndroidFence > ( ) , width , height , transformHint , pendingBufferCount ) } ;
2021-04-16 17:05:24 +02:00
out . Push ( width ) ;
out . Push ( height ) ;
out . Push ( transformHint ) ;
out . Push ( pendingBufferCount ) ;
2021-05-22 19:46:28 +02:00
out . Push ( result ) ;
2020-09-14 16:13:36 +02:00
break ;
2021-04-16 17:05:24 +02:00
}
case TransactionCode : : CancelBuffer : {
CancelBuffer ( in . Pop < i32 > ( ) , in . Pop < AndroidFence > ( ) ) ;
2020-09-14 16:13:36 +02:00
break ;
2021-04-16 17:05:24 +02:00
}
case TransactionCode : : Query : {
u32 queryOut { } ;
auto result { Query ( in . Pop < NativeWindowQuery > ( ) , queryOut ) } ;
out . Push ( queryOut ) ;
out . Push ( result ) ;
2020-09-14 16:13:36 +02:00
break ;
2021-04-16 17:05:24 +02:00
}
case TransactionCode : : Connect : {
2021-07-10 15:38:18 +02:00
bool hasProducerListener { in . Pop < u32 > ( ) ! = 0 } ;
2021-04-16 17:05:24 +02:00
if ( hasProducerListener )
throw exception ( " Callbacks using IProducerListener are not supported " ) ;
u32 width { } , height { } , pendingBufferCount { } ;
NativeWindowTransform transformHint { } ;
auto result { Connect ( in . Pop < NativeWindowApi > ( ) , in . Pop < u32 > ( ) , width , height , transformHint , pendingBufferCount ) } ;
out . Push ( width ) ;
out . Push ( height ) ;
out . Push ( transformHint ) ;
out . Push ( pendingBufferCount ) ;
out . Push ( result ) ;
2020-09-14 16:13:36 +02:00
break ;
2021-04-16 17:05:24 +02:00
}
case TransactionCode : : Disconnect : {
auto result { Disconnect ( in . Pop < NativeWindowApi > ( ) ) } ;
out . Push ( result ) ;
2020-09-14 16:13:36 +02:00
break ;
2021-04-16 17:05:24 +02:00
}
case TransactionCode : : SetPreallocatedBuffer : {
2021-07-04 06:09:53 +02:00
auto result { SetPreallocatedBuffer ( in . Pop < i32 > ( ) , in . PopOptionalFlattenable < GraphicBuffer > ( ) ) } ;
out . Push ( result ) ;
2020-09-14 16:13:36 +02:00
break ;
2021-04-16 17:05:24 +02:00
}
2020-09-14 16:13:36 +02:00
default :
throw exception ( " An unimplemented transaction was called: {} " , static_cast < u32 > ( code ) ) ;
}
}
}