unit ps4_libSceVideoOut; {$mode objfpc}{$H+} interface uses Classes, SysUtils, Controls, ExtCtrls, Interfaces, Forms, LMessages, LCLType, LFQueue, ps4_program, sys_types, ps4_queue, ps4_handles, ps4_libkernel, vulkan, vDevice, vMemory; type PSceVideoOutBufferAttribute=^TSceVideoOutBufferAttribute; TSceVideoOutBufferAttribute=packed record format , tmode , aspect , width , height , pixelPitch , option:DWORD; reserved0:DWORD; reserved1:QWORD; end; PSceVideoOutResolutionStatus=^SceVideoOutResolutionStatus ; SceVideoOutResolutionStatus=packed record width :DWORD; height :DWORD; paneWidth :DWORD; paneHeight :DWORD; refreshRate :QWORD; screenSize :Single; flags :Word; reserved0 :Word; reserved1:array[0..2] of DWORD; end; //Color adjustment PSceVideoOutColorSettings=^SceVideoOutColorSettings; SceVideoOutColorSettings=packed record gamma:array[0..2] of Single; option:DWORD; end; PSceVideoOutFlipStatus=^SceVideoOutFlipStatus; SceVideoOutFlipStatus=packed record count :QWORD; processTime :QWORD; tsc :QWORD; flipArg :Int64; submitTsc :QWORD; _reserved0 :QWORD; gcQueueNum :Longint; flipPendingNum :Longint; currentBuffer :Longint; _reserved1 :DWORD; end; PSceVideoOutVblankStatus=^SceVideoOutVblankStatus; SceVideoOutVblankStatus=packed record count:QWORD; // count of vblanks after sceVideoOutOpen() processTime:QWORD; // processTime of the time of the latest vblank event tsc:QWORD; // Timestamp counter value when the latest vblank executed _reserved:QWORD; flags:Byte; // SceVideoOutVblankStatusFlags pad1:array[0..6] of Byte; end; const //SceVideoOutBufferAttributeOption SCE_VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_NONE = 0; SCE_VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_VR = 7; SCE_VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_STRICT_COLORIMETRY = (1 shl 3); SCE_VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_MASK = (1 shl 4) - 1; SCE_VIDEO_OUT_BUFFER_NUM_MAX = 16; SCE_VIDEO_OUT_BUFFER_ATTRIBUTE_NUM_MAX = 4; SCE_VIDEO_OUT_BUFFER_FLIP_RATE_MAX = 2; SCE_VIDEO_OUT_BUFFER_INDEX_BLANK = -1; // special buffer index to blank screen SCE_VIDEO_OUT_BUFFER_INITIAL_FLIP_ARG = -1; // initial flipArg valu at sceVideoOutOpen() //SceVideoOutResolutionStatusFlags SCE_VIDEO_OUT_RESOLUTION_STATUS_FLAGS_OUTPUT_MASK = (1 shl 0); SCE_VIDEO_OUT_RESOLUTION_STATUS_FLAGS_OUTPUT_IN_USE = (0 shl 0); SCE_VIDEO_OUT_RESOLUTION_STATUS_FLAGS_OUTPUT_NOT_IN_USE = (1 shl 0); //SceVideoOutRefreshRate SCE_VIDEO_OUT_REFRESH_RATE_UNKNOWN = 0; SCE_VIDEO_OUT_REFRESH_RATE_23_98HZ = 1; SCE_VIDEO_OUT_REFRESH_RATE_50HZ = 2; SCE_VIDEO_OUT_REFRESH_RATE_59_94HZ = 3; SCE_VIDEO_OUT_REFRESH_RATE_29_97HZ = 6; // for VR SCE_VIDEO_OUT_REFRESH_RATE_89_91HZ = 35; SCE_VIDEO_OUT_REFRESH_RATE_119_88HZ = 13; SCE_VIDEO_OUT_REFRESH_RATE_ANY = $FFFFFFFFFFFFFFFF; //SceVideoOutBusType SCE_VIDEO_OUT_BUS_TYPE_MAIN = 0; //SceVideoOutPixelFormat SCE_VIDEO_OUT_PIXEL_FORMAT_A8R8G8B8_SRGB = $80000000; // MSB first. Blue is at LSB SCE_VIDEO_OUT_PIXEL_FORMAT_B8_G8_R8_A8_SRGB = SCE_VIDEO_OUT_PIXEL_FORMAT_A8R8G8B8_SRGB; // alias name in a gnm-friendly order (LSB first) SCE_VIDEO_OUT_PIXEL_FORMAT_A16R16G16B16_FLOAT = $C1060000; // MSB first. Blue is at LSB SCE_VIDEO_OUT_PIXEL_FORMAT_B16_G16_R16_A16_FLOAT = SCE_VIDEO_OUT_PIXEL_FORMAT_A16R16G16B16_FLOAT; // alias name in a gnm-friendly order (LSB first) SCE_VIDEO_OUT_PIXEL_FORMAT_A8B8G8R8_SRGB = $80002200; // MSB first. Red is at LSB SCE_VIDEO_OUT_PIXEL_FORMAT_R8_G8_B8_A8_SRGB = SCE_VIDEO_OUT_PIXEL_FORMAT_A8B8G8R8_SRGB; // alias name in a gnm-friendly order (LSB first) SCE_VIDEO_OUT_PIXEL_FORMAT_A2R10G10B10 = $88060000; // MSB first. Blue is at LSB SCE_VIDEO_OUT_PIXEL_FORMAT_B10_G10_R10_A2 = SCE_VIDEO_OUT_PIXEL_FORMAT_A2R10G10B10; // alias name in a gnm-friendly order (LSB first) SCE_VIDEO_OUT_PIXEL_FORMAT_A2R10G10B10_SRGB = $88000000; // MSB first. Blue is at LSB SCE_VIDEO_OUT_PIXEL_FORMAT_B10_G10_R10_A2_SRGB = SCE_VIDEO_OUT_PIXEL_FORMAT_A2R10G10B10_SRGB; // alias name in a gnm-friendly order (LSB first) SCE_VIDEO_OUT_PIXEL_FORMAT_A2R10G10B10_BT2020_PQ = $88740000; // MSB first. Blue is at LSB SCE_VIDEO_OUT_PIXEL_FORMAT_B10_G10_R10_A2_BT2020_PQ = SCE_VIDEO_OUT_PIXEL_FORMAT_A2R10G10B10_BT2020_PQ; // alias name in a gnm-friendly order (LSB first) //SceVideoOutFlipMode SCE_VIDEO_OUT_FLIP_MODE_VSYNC = 1; // on real video out vsync SCE_VIDEO_OUT_FLIP_MODE_HSYNC = 2; // ASAP (but not immediate) SCE_VIDEO_OUT_FLIP_MODE_WINDOW = 3; // similar to vsync but may flip on some windows at the top and the bottom of the display. N/A on Neo mode SCE_VIDEO_OUT_FLIP_MODE_VSYNC_MULTI = 4; // vsync mode but allows multiple flips per vsync. flipRate is not valid. N/A on Neo mode SCE_VIDEO_OUT_FLIP_MODE_VSYNC_MULTI_2 = 5; // vsync mode but allows multiple flips per vsync. flipRate is valid SCE_VIDEO_OUT_FLIP_MODE_WINDOW_2 = 6; // Window mode but the top margin is less accurate than _MODE_WINDOW. The bottom margin must be 0. //SceVideoOutTilingMode SCE_VIDEO_OUT_TILING_MODE_TILE = 0; // 32bpp pixel format only if on Neo mode SCE_VIDEO_OUT_TILING_MODE_LINEAR = 1; // 32bpp pixel format only //SceVideoOutAspectRatio SCE_VIDEO_OUT_ASPECT_RATIO_16_9 = 0; //SceVideoOutEventId SCE_VIDEO_OUT_EVENT_FLIP = 0; SCE_VIDEO_OUT_EVENT_VBLANK = 1; SCE_VIDEO_OUT_EVENT_PRE_VBLANK_START = 2; //#define SCE_KERNEL_EVFILT_TIMER EVFILT_TIMER //#define SCE_KERNEL_EVFILT_READ EVFILT_READ //#define SCE_KERNEL_EVFILT_WRITE EVFILT_WRITE //#define SCE_KERNEL_EVFILT_USER EVFILT_USER //#define SCE_KERNEL_EVFILT_FILE EVFILT_VNODE //#define SCE_KERNEL_EVFILT_GNM EVFILT_GRAPHICS_CORE //#define SCE_KERNEL_EVFILT_VIDEO_OUT EVFILT_DISPLAY //#define SCE_KERNEL_EVFILT_HRTIMER EVFILT_HRTIMER SCE_VIDEO_OUT_CURSOR_IMAGE_ADDRESS_PENDING=0; //Output status for cursor image buffer SCE_VIDEO_OUT_CURSOR_DISABLE_PENDING =1; //Output reflection status for cursor disable processing SCE_VIDEO_OUT_CURSOR_NUM_MAX=2; const SCE_ERROR_ERROR_FLAG=$80000000; //SCE_ERROR_MAKE_ERROR(fac, sts) (SCE_ERROR_ERROR_FLAG | ((fac) << 16) | (sts)) SCE_GNM_ERROR_UNKNOWN=(SCE_ERROR_ERROR_FLAG or ($FF shl 16) or $EE); const SCE_VIDEO_OUT_ERROR_INVALID_VALUE =-2144796671; // 0x80290001 */ SCE_VIDEO_OUT_ERROR_INVALID_ADDRESS =-2144796670; // 0x80290002 */ SCE_VIDEO_OUT_ERROR_INVALID_PIXEL_FORMAT =-2144796669; // 0x80290003 */ SCE_VIDEO_OUT_ERROR_INVALID_PITCH =-2144796668; // 0x80290004 */ SCE_VIDEO_OUT_ERROR_INVALID_RESOLUTION =-2144796667; // 0x80290005 */ SCE_VIDEO_OUT_ERROR_INVALID_FLIP_MODE =-2144796666; // 0x80290006 */ SCE_VIDEO_OUT_ERROR_INVALID_TILING_MODE =-2144796665; // 0x80290007 */ SCE_VIDEO_OUT_ERROR_INVALID_ASPECT_RATIO =-2144796664; // 0x80290008 */ SCE_VIDEO_OUT_ERROR_RESOURCE_BUSY =-2144796663; // 0x80290009 */ SCE_VIDEO_OUT_ERROR_INVALID_INDEX =-2144796662; // 0x8029000A */ SCE_VIDEO_OUT_ERROR_INVALID_HANDLE =-2144796661; // 0x8029000B */ SCE_VIDEO_OUT_ERROR_INVALID_EVENT_QUEUE =-2144796660; // 0x8029000C */ SCE_VIDEO_OUT_ERROR_INVALID_EVENT =-2144796659; // 0x8029000D */ SCE_VIDEO_OUT_ERROR_NO_EMPTY_SLOT =-2144796657; // 0x8029000F */ SCE_VIDEO_OUT_ERROR_SLOT_OCCUPIED =-2144796656; // 0x80290010 */ SCE_VIDEO_OUT_ERROR_FLIP_QUEUE_FULL =-2144796654; // 0x80290012 */ SCE_VIDEO_OUT_ERROR_INVALID_MEMORY =-2144796653; // 0x80290013 */ SCE_VIDEO_OUT_ERROR_MEMORY_NOT_PHYSICALLY_CONTIGUOUS =-2144796652; // 0x80290014 */ SCE_VIDEO_OUT_ERROR_MEMORY_INVALID_ALIGNMENT =-2144796651; // 0x80290015 */ SCE_VIDEO_OUT_ERROR_UNSUPPORTED_OUTPUT_MODE =-2144796650; // 0x80290016 */ SCE_VIDEO_OUT_ERROR_OVERFLOW =-2144796649; // 0x80290017 */ SCE_VIDEO_OUT_ERROR_NO_DEVICE =-2144796648; // 0x80290018 */ SCE_VIDEO_OUT_ERROR_UNAVAILABLE_OUTPUT_MODE =-2144796647; // 0x80290019 */ SCE_VIDEO_OUT_ERROR_INVALID_OPTION =-2144796646; // 0x8029001A */ SCE_VIDEO_OUT_ERROR_PORT_UNSUPPORTED_FUNCTION =-2144796645; // 0x8029001B */ SCE_VIDEO_OUT_ERROR_UNSUPPORTED_OPERATION =-2144796644; // 0x8029001C */ SCE_VIDEO_OUT_ERROR_FATAL =-2144796417; // 0x802900FF */ SCE_VIDEO_OUT_ERROR_UNKNOWN =-2144796418; // 0x802900FE */ SCE_VIDEO_OUT_ERROR_ENOMEM =-2144792564; // 0x8029100C */ function _VideoOutGetBufferAdr(hVideo:Integer;bufferIndex:Integer):Pointer; Procedure App_Run; type PqcFlipInfo=^TqcFlipInfo; TqcFlipInfo=record hVideo:Integer; bufferIndex:Integer; flipMode:Integer; flipArg:Int64; end; function _qc_sceVideoOutSubmitFlip(Flip:PqcFlipInfo):Integer; implementation uses vFlip, sys_signal, sys_time, ps4_time, spinlock, hamt; type PQNode=^TQNode; TOnParent=Procedure(node:PQNode) of object; TQNode=object private next_:PQNode; public lock:Pointer; Parent:TOnParent; wait:SizeUint; u:record Case Byte of 0:(bufferIndex:Integer; flipMode:Integer; flipArg:Int64; _type:Integer); end; end; var FQueueVideoOutCount:PtrUint; FQueueVideoOut:TIntrusiveMPSCQueue; Procedure Push2VideoOut(P:Pointer); begin if FQueueVideoOut.Push(P) then begin System.InterLockedIncrement(Pointer(FQueueVideoOutCount)); if Assigned(WakeMainThread) then WakeMainThread(nil); end; end; type TUserApp=class(TApplication) procedure OnIdleUpdate(Sender:TObject;var Done:Boolean); procedure OnTimer(Sender:TObject); end; procedure TUserApp.OnIdleUpdate(Sender:TObject;var Done:Boolean); var rc:PtrUInt; node:PQNode; begin Done:=True; Node:=nil; rc:=FQueueVideoOutCount; While FQueueVideoOut.Pop(Node) do begin System.InterLockedDecrement(Pointer(FQueueVideoOutCount)); if Assigned(Node^.Parent) then begin Node^.Parent(Node); end; if rc=0 then Break; Dec(rc); if rc=0 then Break; end; end; procedure TUserApp.OnTimer(Sender:TObject); var Done:Boolean; begin OnIdleUpdate(Self,Done); end; var Timer:TTimer; Procedure App_Run; begin Timer:=TTimer.Create(nil); Timer.Interval:=10; Timer.OnTimer:=@TUserApp(Application).OnTimer; Timer.Enabled:=true; Application.OnException:=nil; Application.CaptureExceptions:=False; Application.AddOnIdleHandler(@TUserApp(Application).OnIdleUpdate,False); Application.Scaled:=True; Application.Initialize; Application.Run; end; var FVideoOutMap:TIntegerHandles; type TMyForm=class(TForm) procedure CloseEvent(Sender:TObject;var CloseAction:TCloseAction); procedure WMEraseBkgnd(var Message:TLMEraseBkgnd); message LM_ERASEBKGND; end; TVPos=packed record X,Y:DWORD; end; TVCursor=packed record lock:DWORD; enable:DWORD; Pos:TVPos; adr:Pointer; pending:DWORD; end; TVCursors=array[0..SCE_VIDEO_OUT_CURSOR_NUM_MAX-1] of TVCursor; TVideoOut=class(TClassHandle) FForm:TMyForm; FGpuFlip:TvFlip; FNodePos,FNodesUses:PtrUInt; FNodes:array[0..15] of TQNode; FlipEvents:Thamt64locked; VblankEvents:Thamt64locked; Fgamma:TGamma; Ffps :QWORD; Ftsc_prev :QWORD; VblankStatus:record FprocessTime:QWORD; FTsc :QWORD; end; FlipStatus:record Fcount_flips :QWORD; //Number of flips completed after opening the port FprocessTime :QWORD; //Process time upon completion of the last flip Ftsc_flips :QWORD; //System timestamp counter value when the last flip completed FsubmitTsc :QWORD; //Timestamp counter value when the last completed flip is requested FflipArg :Int64; FgcQueueNum :Longint; //Number of flips where execution is not yet complete for GPU commands issued when the flips were submitted using Gnm::submitAndFlipCommandBuffers() or Gnmx::GfxContext::submitAndFlip() FflipPendingNum:Longint; //Total number of submitted flips that are not completed yet (including gcQueueNum) FcurrentBuffer:Longint; end; //(MAIN port: 1 to 16, AUX port: 1 to 8) FBuffers:record lock:Pointer; addr:array[0..15] of Pointer; attr:array[0..15] of TSceVideoOutBufferAttribute; end; FCursors:array[0..SCE_VIDEO_OUT_CURSOR_NUM_MAX-1] of TVCursor; FlipRate:Byte; function alloc_node:PQNode; procedure free_node(n:PQNode); procedure sceVideoOutOpen(node:PQNode); procedure post_event_flip(flipArg:Int64); procedure post_event_vblank(flipArg:Int64); procedure sceVideoOutSubmitFlip(node:PQNode); Constructor Create; Destructor Destroy; override; end; procedure _on_free_kevent(data,userdata:Pointer); begin if (data=nil) then Exit; _free_kevent_node(PKEventNode(data)); end; Destructor TVideoOut.Destroy; begin FreeAndNil(FGpuFlip); FreeAndNil(FForm); FlipEvents.LockWr; HAMT_clear64(@FlipEvents.hamt,@_on_free_kevent,nil); VblankEvents.LockWr; HAMT_clear64(@VblankEvents.hamt,@_on_free_kevent,nil); inherited; end; const pd_Width=1280; pd_Height=720; //pd_Width=1312; //pd_Height=738; //pd_Width=1440; //pd_Height=810; //pd_Width=1920; //pd_Height=1080; function TVideoOut.alloc_node:PQNode; var pos:PtrUInt; n:PQNode; begin Result:=nil; repeat if (FNodesUses=Length(FNodes)) then Exit; pos:=FNodePos; n:=@FNodes[pos mod Length(FNodes)]; if spin_trylock(n^.lock) then begin System.InterlockedIncrement(Pointer(FNodesUses)); FNodePos:=pos+1; Acqure; Exit(n); end; System.ThreadSwitch; until false; end; procedure TVideoOut.free_node(n:PQNode); begin if (self=nil) or (n=nil) then Exit; System.InterlockedDecrement(Pointer(FNodesUses)); spin_unlock(n^.lock); Release; end; // procedure TMyForm.CloseEvent(Sender:TObject;var CloseAction:TCloseAction); begin //Halt; Application.Terminate; end; procedure TMyForm.WMEraseBkgnd(var Message:TLMEraseBkgnd); begin Message.Result:=1; end; procedure TVideoOut.sceVideoOutOpen(node:PQNode); begin VblankStatus.FprocessTime:=ps4_sceKernelReadTsc; VblankStatus.FTsc :=ps4_sceKernelGetProcessTime; Writeln('sceVideoOutOpen:',HexStr(Pointer(Self))); FForm:=TMyForm.CreateNew(nil); FForm.ShowInTaskBar:=stAlways; FForm.DoubleBuffered:=False; FForm.ParentDoubleBuffered:=False; FForm.FormStyle:=fsNormal; FForm.SetBounds(100, 100, pd_Width, pd_Height); FForm.Caption:='fpPS4'; FForm.OnClose:=@FForm.CloseEvent; Application.UpdateMainForm(FForm); FForm.Show; FGpuFlip:=TvFlip.Create(FForm.Handle); FGpuFlip.FNeoMode:=ps4_sceKernelIsNeoMode<>0; //data? nop free_node(node); end; Constructor TVideoOut.Create; const Single1:Single=1; begin FlipEvents.Init; VblankEvents.Init; FlipStatus.FflipArg:=SCE_VIDEO_OUT_BUFFER_INITIAL_FLIP_ARG; FlipStatus.FcurrentBuffer:=SCE_VIDEO_OUT_BUFFER_INDEX_BLANK; FillDWord(Fgamma,3,PDWORD(@Single1)^); end; function ps4_sceVideoOutOpen(userID,busType,index:Integer;param:Pointer):Integer; SysV_ABI_CDecl; var H:TVideoOut; node:PQNode; begin _sig_lock; H:=TVideoOut.Create; node:=H.alloc_node; Result:=-1; FVideoOutMap.New(H,Result); Writeln('>sceVideoOutOpen:',HexStr(Pointer(H))); node^.Parent:=@H.sceVideoOutOpen; Push2VideoOut(node); H.Release; Writeln('sceVideoOutOpen:',userID,' ',busType); _sig_unlock; end; function ps4_sceVideoOutClose(handle:Integer):Integer; SysV_ABI_CDecl; begin Result:=0; _sig_lock; if not FVideoOutMap.Delete(handle) then Result:=SCE_VIDEO_OUT_ERROR_INVALID_HANDLE; _sig_unlock; end; procedure ps4_sceVideoOutSetBufferAttribute; assembler; nostackframe; label _end; asm test %rdi,%rdi je _end mov %esi, (%rdi) //format mov %edx, 0x4(%rdi) //tmode mov %ecx, 0x8(%rdi) //aspect mov %r8d, 0xC(%rdi) //width mov %r9d,0x10(%rdi) //height mov 0x08(%rsp),%eax //pixelPitch _end: end; //SceKernelEvent=packed record // ident:PtrUint; //SCE_VIDEO_OUT_EVENT_FLIP // filter:SmallInt; //SCE_KERNEL_EVFILT_VIDEO_OUT // flags:Word; //0 // fflags:DWORD; //0 // data:Ptrint; //flipArg 48 bits // udata:Pointer; //udata //end; function ps4_sceVideoOutGetEventCount(ev:PSceKernelEvent):Integer; SysV_ABI_CDecl; begin if (ev=nil) then Exit(SCE_VIDEO_OUT_ERROR_INVALID_ADDRESS); if (ev^.filter<>SCE_KERNEL_EVFILT_VIDEO_OUT) then Exit(SCE_VIDEO_OUT_ERROR_INVALID_EVENT); Result:=(ev^.data shr 12) and $F; end; function ps4_sceVideoOutGetEventData(ev:PSceKernelEvent;data:Pint64):Integer; SysV_ABI_CDecl; var ret:int64; begin if (ev=nil) or (data=nil) then Exit(SCE_VIDEO_OUT_ERROR_INVALID_ADDRESS); if (ev^.filter<>SCE_KERNEL_EVFILT_VIDEO_OUT) then Exit(SCE_VIDEO_OUT_ERROR_INVALID_EVENT); ret:=ev^.data shr 16; if (ev^.data>=0) or (ev^.ident=0) then begin data^:=ret; end else begin data^:=ret-$1000000000000; end; Result:=0; end; function ps4_sceVideoOutGetEventId(ev:PSceKernelEvent):Integer; SysV_ABI_CDecl; begin if (ev=nil) then Exit(SCE_VIDEO_OUT_ERROR_INVALID_ADDRESS); if (ev^.filter<>SCE_KERNEL_EVFILT_VIDEO_OUT) then Exit(SCE_VIDEO_OUT_ERROR_INVALID_EVENT); case ev^.ident of 0:Result:=0; 1:Result:=1; 2:Result:=2; else Result:=SCE_VIDEO_OUT_ERROR_INVALID_EVENT; end; end; function ps4_sceVideoOutAddFlipEvent(eq:SceKernelEqueue;hVideo:Integer;udata:Pointer):Integer; SysV_ABI_CDecl; var H:TVideoOut; P:PPointer; node:PKEventNode; begin if (eq=nil) then Exit(SCE_VIDEO_OUT_ERROR_INVALID_EVENT_QUEUE); _sig_lock; H:=TVideoOut(FVideoOutMap.Acqure(hVideo)); _sig_unlock; if (H=nil) then Exit(SCE_VIDEO_OUT_ERROR_INVALID_HANDLE); _sig_lock; H.FlipEvents.LockWr; P:=HAMT_search64(@H.FlipEvents.hamt,QWORD(eq)); if (P<>nil) then begin node:=P^; node^.ev.udata:=udata; end else begin node:=_alloc_kevent_node(eq,SizeOf(TKEventNode)); if (node=nil) or (node=Pointer(1)) then begin H.FlipEvents.Unlock; H.Release; _sig_unlock; Exit(SCE_VIDEO_OUT_ERROR_INVALID_EVENT_QUEUE); end; node^.ev.ident :=SCE_VIDEO_OUT_EVENT_FLIP; node^.ev.filter:=SCE_KERNEL_EVFILT_VIDEO_OUT; node^.ev.udata :=udata; HAMT_insert64(@H.FlipEvents.hamt,QWORD(eq),node); end; H.FlipEvents.Unlock; Writeln('sceVideoOutAddFlipEvent:',HexStr(udata)); Result:=0; H.Release; _sig_unlock; end; function ps4_sceVideoOutAddVblankEvent(eq:SceKernelEqueue;hVideo:Integer;udata:Pointer):Integer; SysV_ABI_CDecl; var H:TVideoOut; P:PPointer; node:PKEventNode; begin if (eq=nil) then Exit(SCE_VIDEO_OUT_ERROR_INVALID_EVENT_QUEUE); _sig_lock; H:=TVideoOut(FVideoOutMap.Acqure(hVideo)); _sig_unlock; if (H=nil) then Exit(SCE_VIDEO_OUT_ERROR_INVALID_HANDLE); _sig_lock; H.VblankEvents.LockWr; P:=HAMT_search64(@H.VblankEvents.hamt,QWORD(eq)); if (P<>nil) then begin node:=P^; node^.ev.udata:=udata; end else begin node:=_alloc_kevent_node(eq,SizeOf(TKEventNode)); if (node=nil) or (node=Pointer(1)) then begin H.VblankEvents.Unlock; H.Release; _sig_unlock; Exit(SCE_VIDEO_OUT_ERROR_INVALID_EVENT_QUEUE); end; node^.ev.ident :=SCE_VIDEO_OUT_EVENT_VBLANK; node^.ev.filter:=SCE_KERNEL_EVFILT_VIDEO_OUT; node^.ev.udata :=udata; HAMT_insert64(@H.VblankEvents.hamt,QWORD(eq),node); end; H.VblankEvents.Unlock; Writeln('sceVideoOutAddVblankEvent:',HexStr(udata)); Result:=0; H.Release; _sig_unlock; end; function __sceVideoOutRegisterBuffers(hVideo:Integer; index:Integer; addr:PPointer; num:Integer; attr:PSceVideoOutBufferAttribute ):Integer; var H:TVideoOut; buf:TvPointer; i,s:Integer; begin For i:=0 to num-1 do begin if not TryGetHostPointerByAddr(addr[i],buf) then Exit(SCE_VIDEO_OUT_ERROR_INVALID_MEMORY); end; H:=TVideoOut(FVideoOutMap.Acqure(hVideo)); if (H=nil) then Exit(SCE_VIDEO_OUT_ERROR_INVALID_HANDLE); spin_lock(H.FBuffers.lock); s:=index+num-1; For i:=index to s do begin if (H.FBuffers.addr[i]<>nil) then begin spin_unlock(H.FBuffers.lock); H.Release; Exit(SCE_VIDEO_OUT_ERROR_SLOT_OCCUPIED); end; end; For i:=index to s do begin H.FBuffers.addr[i]:=addr[i-index]; H.FBuffers.attr[i]:=attr^; end; spin_unlock(H.FBuffers.lock); H.Release; Result:=0; end; function ps4_sceVideoOutRegisterBuffers(hVideo:Integer; index:Integer; addr:PPointer; num:Integer; attr:PSceVideoOutBufferAttribute ):Integer; SysV_ABI_CDecl; begin if (addr=nil) or (attr=nil) then Exit(SCE_VIDEO_OUT_ERROR_INVALID_VALUE); Case index of 0..15:; else Exit(SCE_VIDEO_OUT_ERROR_INVALID_INDEX); end; if (num<0) then Exit(SCE_VIDEO_OUT_ERROR_INVALID_INDEX); Case (index+num) of 1..16:; else Exit(SCE_VIDEO_OUT_ERROR_INVALID_INDEX); end; case attr^.format of SCE_VIDEO_OUT_PIXEL_FORMAT_A8R8G8B8_SRGB :; SCE_VIDEO_OUT_PIXEL_FORMAT_A16R16G16B16_FLOAT :; SCE_VIDEO_OUT_PIXEL_FORMAT_A8B8G8R8_SRGB :; SCE_VIDEO_OUT_PIXEL_FORMAT_A2R10G10B10 :; SCE_VIDEO_OUT_PIXEL_FORMAT_A2R10G10B10_SRGB :; SCE_VIDEO_OUT_PIXEL_FORMAT_A2R10G10B10_BT2020_PQ:; else Exit(SCE_VIDEO_OUT_ERROR_INVALID_PIXEL_FORMAT); end; Case attr^.tmode of SCE_VIDEO_OUT_TILING_MODE_LINEAR:; SCE_VIDEO_OUT_TILING_MODE_TILE:; else Exit(SCE_VIDEO_OUT_ERROR_INVALID_TILING_MODE); end; if (attr^.aspect<>SCE_VIDEO_OUT_ASPECT_RATIO_16_9) then Exit(SCE_VIDEO_OUT_ERROR_INVALID_ASPECT_RATIO); if (attr^.width=0) or (attr^.height=0) then Exit(SCE_VIDEO_OUT_ERROR_INVALID_RESOLUTION); if (attr^.pixelPitch=0) then Exit(SCE_VIDEO_OUT_ERROR_INVALID_PITCH); {Case attr^.option of SCE_VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_NONE :; SCE_VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_VR :; SCE_VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_STRICT_COLORIMETRY:; else Exit(SCE_VIDEO_OUT_ERROR_INVALID_OPTION); end;} _sig_lock; Result:=__sceVideoOutRegisterBuffers(hVideo, index, addr, num, attr); _sig_unlock; end; function ps4_sceVideoOutColorSettingsSetGamma_(P:PSceVideoOutColorSettings; gamma:Single; sizeOfSettings:DWORD):Integer; SysV_ABI_CDecl; const Single1:Single=1; begin if (P=nil) then Exit(SCE_VIDEO_OUT_ERROR_INVALID_VALUE); sizeOfSettings:=sizeOfSettings div SizeOf(Single); if (sizeOfSettings=0) then Exit(SCE_VIDEO_OUT_ERROR_INVALID_VALUE); if (gamma<0.1) then gamma:=0.1; if (gamma>2.0) then gamma:=2.0; FillDWord(P^,sizeOfSettings,PDWORD(@Single1)^); if (sizeOfSettings>3) then sizeOfSettings:=3; FillDWord(P^,sizeOfSettings,PDWORD(@gamma)^); //Writeln('SetGamma:',HexStr(P),' ',gamma); Result:=0; end; function ps4_sceVideoOutAdjustColor_(handle:Integer; pSettings:PSceVideoOutColorSettings; sizeOfSettings:DWORD):Integer; SysV_ABI_CDecl; var H:TVideoOut; begin if (pSettings=nil) then Exit(SCE_VIDEO_OUT_ERROR_INVALID_ADDRESS); sizeOfSettings:=sizeOfSettings div SizeOf(Single); if (sizeOfSettings=0) then Exit(SCE_VIDEO_OUT_ERROR_INVALID_VALUE); if (sizeOfSettings>3) then sizeOfSettings:=3; _sig_lock; H:=TVideoOut(FVideoOutMap.Acqure(handle)); _sig_unlock; if (H=nil) then Exit(SCE_VIDEO_OUT_ERROR_INVALID_HANDLE); Move(pSettings^.gamma,H.Fgamma,sizeOfSettings*SizeOf(Single)); if Assigned(H.FGpuFlip) then begin H.FGpuFlip.SetGamma(H.Fgamma); end; _sig_lock; H.Release; _sig_unlock; //Writeln('AdjustColor:',handle,' ',HexStr(pSettings),' ',sizeOfSettings); Result:=0; end; function ps4_sceVideoOutGetResolutionStatus(hVideo:Integer;status:PSceVideoOutResolutionStatus):Integer; SysV_ABI_CDecl; const //pd_Width=1312; //pd_Height=738; //pd_Width=1440; //pd_Height=810; pd_Width=1920; pd_Height=1080; begin if (status=nil) then Exit(SCE_VIDEO_OUT_ERROR_INVALID_ADDRESS); Writeln('sceVideoOutGetResolutionStatus:',hVideo); status^:=Default(SceVideoOutResolutionStatus); status^.width :=pd_Width; status^.height :=pd_Height; status^.paneWidth :=pd_Width; status^.paneHeight :=pd_Height; status^.refreshRate :=SCE_VIDEO_OUT_REFRESH_RATE_59_94HZ; status^.screenSize :=32; //screenSizeInInch status^.flags :=SCE_VIDEO_OUT_RESOLUTION_STATUS_FLAGS_OUTPUT_IN_USE; Result:=0; end; function ps4_sceVideoOutSetFlipRate(hVideo:Integer;rate:Integer):Integer; SysV_ABI_CDecl; Const rateTable:array[0..2] of Byte=(60, 30, 20); var H:TVideoOut; begin Writeln('sceVideoOutSetFlipRate:',rate); case rate of 0..2: begin _sig_lock; H:=TVideoOut(FVideoOutMap.Acqure(hVideo)); _sig_unlock; if (H=nil) then Exit(SCE_VIDEO_OUT_ERROR_INVALID_HANDLE); H.FlipRate:=rateTable[rate]; H.Release; Writeln('Rate:',rateTable[rate]); Result:=0; end; else Result:=SCE_VIDEO_OUT_ERROR_INVALID_VALUE; end; end; function ps4_sceVideoOutGetFlipStatus(hVideo:Integer;status:PSceVideoOutFlipStatus):Integer; SysV_ABI_CDecl; var H:TVideoOut; begin _sig_lock; H:=TVideoOut(FVideoOutMap.Acqure(hVideo)); _sig_unlock; if (H=nil) then Exit(SCE_VIDEO_OUT_ERROR_INVALID_HANDLE); status^:=Default(SceVideoOutFlipStatus); status^.count :=H.FlipStatus.Fcount_flips ; status^.processTime :=H.FlipStatus.FprocessTime ; status^.tsc :=H.FlipStatus.Ftsc_flips ; status^.submitTsc :=H.FlipStatus.FsubmitTsc ; status^.flipArg :=H.FlipStatus.FflipArg ; status^.gcQueueNum :=H.FlipStatus.FgcQueueNum ; status^.flipPendingNum:=H.FlipStatus.FflipPendingNum; status^.currentBuffer :=H.FlipStatus.FcurrentBuffer ; Result:=0; H.Release; end; function ps4_sceVideoOutIsFlipPending(hVideo:Integer):Integer; SysV_ABI_CDecl; var H:TVideoOut; begin _sig_lock; H:=TVideoOut(FVideoOutMap.Acqure(hVideo)); _sig_unlock; if (H=nil) then Exit(SCE_VIDEO_OUT_ERROR_INVALID_HANDLE); Result:=H.FlipStatus.FflipPendingNum; H.Release; end; function _VideoOutGetBufferAdr(hVideo:Integer;bufferIndex:Integer):Pointer; var H:TVideoOut; begin Result:=nil; H:=TVideoOut(FVideoOutMap.Acqure(hVideo)); if (H=nil) then Exit; if (bufferIndex>=0) and (bufferIndex0) then begin time:=(1000000 div (FlipRate+1)); //+5 selected empirically //time:=time-1300; //time:=time-1300; elap:=SwTimePassedUnits(FlipStatus.Ftsc_flips); elap:=(elap+9) div 10; //elap:=elap+(elap div 100)*14; if (elap=SCE_VIDEO_OUT_BUFFER_NUM_MAX)) and (bufferIndex<>SCE_VIDEO_OUT_BUFFER_INDEX_BLANK) then begin Exit(SCE_VIDEO_OUT_ERROR_INVALID_INDEX); end; _sig_lock; H:=TVideoOut(FVideoOutMap.Acqure(hVideo)); _sig_unlock; if (H=nil) then Exit(SCE_VIDEO_OUT_ERROR_INVALID_HANDLE); _sig_lock; node:=H.alloc_node; _sig_unlock; if (node=nil) then begin H.Release; Exit(SCE_VIDEO_OUT_ERROR_FLIP_QUEUE_FULL); end; node^.Parent:=@H.sceVideoOutSubmitFlip; node^.u.bufferIndex:=bufferIndex; node^.u.flipMode :=flipMode; node^.u.flipArg :=flipArg; node^.u._type :=0; node^.wait :=0; System.InterlockedIncrement(H.FlipStatus.FflipPendingNum); //H.FsubmitTsc:=ps4_sceKernelReadTsc; //Timestamp counter value when the last completed flip is requested //Writeln('submit_event_flip'); _sig_lock; Push2VideoOut(node); wait_until_equal(node^.wait,0); H.FlipStatus.FsubmitTsc:=ps4_sceKernelReadTsc; //Timestamp counter value when the last completed flip is requested H.Release; _sig_unlock; end; function _qc_sceVideoOutSubmitFlip(Flip:PqcFlipInfo):Integer; var H:TVideoOut; node:PQNode; begin Result:=0; if (Flip=nil) then Exit; if ((Flip^.bufferIndex<0) or (Flip^.bufferIndex>=SCE_VIDEO_OUT_BUFFER_NUM_MAX)) and (Flip^.bufferIndex<>SCE_VIDEO_OUT_BUFFER_INDEX_BLANK) then begin Exit(SCE_VIDEO_OUT_ERROR_INVALID_INDEX); end; H:=TVideoOut(FVideoOutMap.Acqure(Flip^.hVideo)); if (H=nil) then Exit(SCE_VIDEO_OUT_ERROR_INVALID_HANDLE); node:=H.alloc_node; if (node=nil) then begin H.Release; Exit(SCE_VIDEO_OUT_ERROR_FLIP_QUEUE_FULL); end; node^.Parent:=@H.sceVideoOutSubmitFlip; node^.u.bufferIndex:=Flip^.bufferIndex; node^.u.flipMode :=Flip^.flipMode; node^.u.flipArg :=Flip^.flipArg; node^.u._type :=1; node^.wait :=0; System.InterlockedIncrement(H.FlipStatus.FgcQueueNum); System.InterlockedIncrement(H.FlipStatus.FflipPendingNum); //H.FsubmitTsc:=ps4_sceKernelReadTsc; //Timestamp counter value when the last completed flip is requested //Writeln('submit_event_flip'); Push2VideoOut(node); wait_until_equal(node^.wait,0); H.FlipStatus.FsubmitTsc:=ps4_sceKernelReadTsc; //Timestamp counter value when the last completed flip is requested H.Release; end; function ps4_sceVideoOutCursorSetPosition(hVideo:Integer;index:Integer;posX,posY:DWORD):Integer; SysV_ABI_CDecl; var H:TVideoOut; P:TVPos; begin Result:=0; if (index<0) or (index>=SCE_VIDEO_OUT_CURSOR_NUM_MAX) then Exit(SCE_VIDEO_OUT_ERROR_INVALID_INDEX); _sig_lock; H:=TVideoOut(FVideoOutMap.Acqure(hVideo)); _sig_unlock; if (H=nil) then Exit(SCE_VIDEO_OUT_ERROR_INVALID_HANDLE); P.X:=posX; P.Y:=posY; System.InterlockedExchange64(QWORD(H.FCursors[index].Pos),QWORD(P)); _sig_lock; H.Release; _sig_unlock; end; function __sceVideoOutCursorEnable(hVideo:Integer;index:Integer;address:Pointer):Integer; var H:TVideoOut; buf:TvPointer; begin Result:=0; if (index<0) or (index>=SCE_VIDEO_OUT_CURSOR_NUM_MAX) then Exit(SCE_VIDEO_OUT_ERROR_INVALID_INDEX); if not IsAlign(address,4*1024) then Exit(SCE_VIDEO_OUT_ERROR_INVALID_ADDRESS); if not TryGetHostPointerByAddr(address,buf) then Exit(SCE_VIDEO_OUT_ERROR_INVALID_MEMORY); H:=TVideoOut(FVideoOutMap.Acqure(hVideo)); if (H=nil) then Exit(SCE_VIDEO_OUT_ERROR_INVALID_HANDLE); spin_trylock(H.FCursors[index].lock); H.FCursors[index].enable:=1; H.FCursors[index].adr:=address; spin_unlock(H.FCursors[index].lock); H.Release; end; function ps4_sceVideoOutCursorEnable(hVideo:Integer;index:Integer;address:Pointer):Integer; SysV_ABI_CDecl; begin _sig_lock; Result:=__sceVideoOutCursorEnable(hVideo,index,address); _sig_unlock; end; function __sceVideoOutCursorSetImageAddress(hVideo:Integer;index:Integer;address:Pointer):Integer; var H:TVideoOut; buf:TvPointer; begin Result:=0; if (index<0) or (index>=SCE_VIDEO_OUT_CURSOR_NUM_MAX) then Exit(SCE_VIDEO_OUT_ERROR_INVALID_INDEX); H:=TVideoOut(FVideoOutMap.Acqure(hVideo)); if (H=nil) then Exit(SCE_VIDEO_OUT_ERROR_INVALID_HANDLE); if not IsAlign(address,4*1024) then Exit(SCE_VIDEO_OUT_ERROR_INVALID_ADDRESS); if not TryGetHostPointerByAddr(address,buf) then Exit(SCE_VIDEO_OUT_ERROR_INVALID_MEMORY); spin_trylock(H.FCursors[index].lock); H.FCursors[index].adr:=address; spin_unlock(H.FCursors[index].lock); H.Release; end; function ps4_sceVideoOutCursorSetImageAddress(hVideo:Integer;index:Integer;address:Pointer):Integer; SysV_ABI_CDecl; begin _sig_lock; Result:=__sceVideoOutCursorSetImageAddress(hVideo,index,address); _sig_unlock; end; function ps4_sceVideoOutCursorIsUpdatePending(hVideo:Integer;index:Integer;_type:DWORD):Integer; SysV_ABI_CDecl; var H:TVideoOut; begin Result:=0; if (index<0) or (index>=SCE_VIDEO_OUT_CURSOR_NUM_MAX) then Exit(SCE_VIDEO_OUT_ERROR_INVALID_INDEX); _sig_lock; H:=TVideoOut(FVideoOutMap.Acqure(hVideo)); _sig_unlock; if (H=nil) then Exit(SCE_VIDEO_OUT_ERROR_INVALID_HANDLE); Case _type of SCE_VIDEO_OUT_CURSOR_IMAGE_ADDRESS_PENDING:if (H.FCursors[index].pending<>0) then Result:=1; SCE_VIDEO_OUT_CURSOR_DISABLE_PENDING :if (H.FCursors[index].adr<>nil) then Result:=1; else Result:=SCE_VIDEO_OUT_ERROR_INVALID_VALUE; end; _sig_lock; H.Release; _sig_unlock; end; function ps4_sceVideoOutGetVblankStatus(hVideo:Integer;status:PSceVideoOutVblankStatus):Integer; SysV_ABI_CDecl; var H:TVideoOut; elap:QWORD; pc,pf:QWORD; proc:QWORD; count:QWORD; time:DWORD; hz:Byte; begin _sig_lock; H:=TVideoOut(FVideoOutMap.Acqure(hVideo)); _sig_unlock; if (H=nil) then Exit(SCE_VIDEO_OUT_ERROR_INVALID_HANDLE); hz:=H.FlipRate; if (hz=0) then hz:=60; time:=(1000000 div hz); elap:=SwTimePassedUnits(H.VblankStatus.FTsc); elap:=(elap+9) div 10; count:=elap div time; proc:=ps4_sceKernelGetProcessTime; proc:=proc-H.VblankStatus.FprocessTime; proc:=AlignDw(proc,time); proc:=proc+H.VblankStatus.FprocessTime; SwQueryPerformanceCounter(pc,pf); pc:=pc-H.VblankStatus.FTsc; time:=(pf div hz); pc:=AlignDw(pc,time); pc:=pc+H.VblankStatus.FTsc; status^:=Default(SceVideoOutVblankStatus); status^.count :=count; status^.processTime :=proc; status^.tsc :=pc; status^.flags :=0; Result:=0; _sig_lock; H.Release; _sig_unlock; end; function ps4_sceVideoOutSetWindowModeMargins(hVideo:Integer;top,bottom:Integer):Integer; SysV_ABI_CDecl; begin Result:=0; end; // function Load_libSceVideoOut(Const name:RawByteString):TElf_node; var lib:PLIBRARY; begin Result:=TElf_node.Create; Result.pFileName:=name; lib:=Result._add_lib('libSceVideoOut'); lib^.set_proc($529DFA3D393AF3B1,@ps4_sceVideoOutOpen); lib^.set_proc($BAAB951F8FC3BBBF,@ps4_sceVideoOutClose); lib^.set_proc($1D7CE32BDC88DF49,@ps4_sceVideoOutAddFlipEvent); lib^.set_proc($5EBBBDDB01C94668,@ps4_sceVideoOutAddVblankEvent); lib^.set_proc($8BAFEC47DD56B7FE,@ps4_sceVideoOutSetBufferAttribute); lib^.set_proc($C37058FAD0048906,@ps4_sceVideoOutRegisterBuffers); lib^.set_proc($0D886159B2527918,@ps4_sceVideoOutColorSettingsSetGamma_); lib^.set_proc($A6FF42239542F91D,@ps4_sceVideoOutAdjustColor_); lib^.set_proc($EA43E78F9D53EB66,@ps4_sceVideoOutGetResolutionStatus); lib^.set_proc($0818AEE26084D430,@ps4_sceVideoOutSetFlipRate); lib^.set_proc($49B537770A7CD254,@ps4_sceVideoOutGetFlipStatus); lib^.set_proc($CE05E27C74FD12B6,@ps4_sceVideoOutIsFlipPending); lib^.set_proc($538E8DC0E889A72B,@ps4_sceVideoOutSubmitFlip); lib^.set_proc($375EC02BCF0D743D,@ps4_sceVideoOutCursorSetPosition); lib^.set_proc($50F656087F2A4CCE,@ps4_sceVideoOutCursorEnable); lib^.set_proc($BBFF5B856400A6AF,@ps4_sceVideoOutCursorSetImageAddress); lib^.set_proc($1E26CEB5ECF34FA3,@ps4_sceVideoOutCursorIsUpdatePending); lib^.set_proc($D456412B2F0778D5,@ps4_sceVideoOutGetVblankStatus); lib^.set_proc($313C71ACE09E4A28,@ps4_sceVideoOutSetWindowModeMargins); end; initialization FQueueVideoOut.Create; FVideoOutMap:=TIntegerHandles.Create; ps4_app.RegistredPreLoad('libSceVideoOut.prx',@Load_libSceVideoOut); end.