Merge branch 'master' into master

This commit is contained in:
starfrost
2025-09-24 21:02:47 +01:00
committed by GitHub
104 changed files with 24413 additions and 38 deletions

View File

@@ -139,7 +139,15 @@ option(DISCORD "Discord Rich Presence support"
option(DEBUGREGS486 "Enable debug register opeartion on 486+ CPUs" OFF)
option(LIBASAN "Enable compilation with the addresss sanitizer" OFF)
if((ARCH STREQUAL "arm64"))
if (NV_LOG)
add_compile_definitions(ENABLE_NV_LOG)
endif()
if (NV_LOG_ULTRA)
add_compile_definitions(ENABLE_NV_LOG_ULTRA)
endif()
if((ARCH STREQUAL "arm64") OR (ARCH STREQUAL "arm"))
set(NEW_DYNAREC ON)
else()
option(NEW_DYNAREC "Use the PCem v15 (\"new\") dynamic recompiler" OFF)
@@ -184,9 +192,12 @@ cmake_dependent_option(PCL "Generic PCL5e Printer"
cmake_dependent_option(SIO_DETECT "Super I/O Detection Helper" ON "DEV_BRANCH" OFF)
cmake_dependent_option(WACOM "Wacom Input Devices" ON "DEV_BRANCH" OFF)
cmake_dependent_option(XL24 "ATI VGA Wonder XL24 (ATI-28800-6)" ON "DEV_BRANCH" OFF)
cmake_dependent_option(NV3 "NVidia RIVA 128/128ZX (NV3/NV3T)" ON "DEV_BRANCH" OFF)
cmake_dependent_option(NETSWITCH "Network Switch Support" ON "DEV_BRANCH" OFF)
cmake_dependent_option(VFIO "Virtual Function I/O" ON "DEV_BRANCH" OFF)
# Ditto but for Qt
if(QT)
option(USE_QT6 "Use Qt6 instead of Qt5" OFF)

View File

@@ -35,7 +35,10 @@
{
"name": "debug",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
"CMAKE_BUILD_TYPE": "Debug",
"NV_LOG": "ON",
"NV_LOG_ULTRA": "ON"
},
"inherits": "base"
},

View File

@@ -0,0 +1 @@
THIS IS FIFOSERVICE!!!!!!!!!!!!

View File

@@ -0,0 +1 @@
B = BUFFER. REMEMBER THESE!!!

View File

@@ -0,0 +1,6 @@
How to optimise riva 128 applications:
* Ensure any set of polygons with one texture is close to a multiple of 128 polygons.
* Try to sort areas of a model with one texture to as close to 128 polygons as possible for efficient submission due to the lack of texturing.
* Try to have around (32*128) for nv3 or (64*128) for nv3t polygons
* Get coding
* Alcohol

View File

@@ -0,0 +1,81 @@
Memory map (RIVA 128)
32 megabytes of MMIO starting at <nvAddr> 0x0000000-0x01FFFFFF
MC (Master Control) 0x00000000-0x00000FFF
Config/Boot 0x00000000-0x000000FF
Master Config 0x00000100-0x00000FFF
Also used to define DMA channel IDs?
MPU-401 I/O 0x00000330-0x00000331 Probably NV1 leftover, NV1 has mpu401 emulation -- no audio in nv3
VGA emulated ports 0x000003B0-0x000003DF Emulated I/O ports for VGA
Bus Control (PBUS) 0x00001000-0x00001FFF
FIFO (PFIFO) 0x00002000-0x00003FFF Submit starting at 0x00800000. Used to configure RAMHT,RAMFC,RAMRO structures & cache
PRM 0x00004000-0x00005FFF Realmode DOS device support
PRMIO 0x00007000-0x00007FFF Realmode access to PCI BAR (Base Address Register) + PCI I/O
PTIMER 0x00009000-0x00009FFF Timer
VGA emulation 0x0000A000-0x0000BFFF
VGA vram emulation 0x000A0000-0x000BFFFF (PRMVGA)
VGA 0x000C0000-0x000C7FFF VGA sequencer + graph controller registers (PRMVIO)
(All of this up to 0x0000FFFF can be traced)
PFB (Framebuffer) 0x00100000-0x00100FFF Interface to vram
PEXTDEV 0x00101000-0x00101FFF External device interface - contains straps
Straps 0x00101000 11 bits of cfg
ROM (VBIOS?) 0x00110000-0x0011FFFF
PALT (?) 0x00120000-0x00120FFF
PEXTDEV 0x00101000-0x00101FFF External Devices
Media Engine (PME) 0x00200000-0x00200FFF Allows for external video capture according to envytools?
PGRAPH (3D rendering) 0x00400000-0x00401FFF
PGRAPH objects (using RAMHT???)
*i assume that when you submit an object these are the registers used to actually draw the current object
Beta blending factor 0x00410000-0x00411FFF
ROP 0x00420000-0x00421FFF Global bitwise operation (Render OPeration) for filtering the final pixel
Color Key 0x00430000-0x00431FFF
Plane Switch 0x00440000-0x00441FFF Something to do with color formats and objects?
Clipping 0x00450000-0x00451FFF
Blend Pattern 0x00460000-0x00461FFF Used for specific blending modes
Quad [OBSOLETE] 0x00470000-0x00471FFF A rectangle. NV1 LEFTOVER, OBSOLETE
Point 0x00480000-0x00481FFF A single point
Line 0x00490000-0x00491FFF A line (with an optional colour). Can also draw a polygon made out of lines - polyline
Lin 0x004A0000-0x004A1FFF A line, without starting or ending pixel (with an optional colour). Can also draw a "polylin"
Triangle [OBSOLETE] 0x004B0000-0x004B1FFF A triangle. NV1 LEFTOVER, OBSOLETE?
Win95 GDI text 0x004C0000-0x004C1FFF Win95 text acceleration
Memory to memory xfer 0x004D0000-0x004D1FFF Represents a memory to memory transfer
Scaled image from vram 0x004E0000-0x004E1FFF Scaled image from GPU VRAM
Image blit from vram 0x00500000-0x00501FFF Image from GPU VRAM
Image blit from cpu 0x00510000-0x00511FFF Image from CPU
Bitmap from cpu 0x00520000-0x00521FFF Bitmap from CPU
Image to memory 0x00540000-0x00541FFF Image to GPU VRAM
Stretch image from cpu 0x00550000-0x00551FFF Stretched image from CPU
Direct3D 5.0 triangle 0x00570000-0x00571FFF A triangle optimised explicitly for directx3/directx5 rendering - supercedes UTRI
PointZ 0x00580000-0x00581FFF A single point with "zeta factor" (not sure what this is yet)
Image in memory 0x005C0000-0x005C0FFF Image in vram(?)
PVIDEO (Video Control) 0x00680000-0x006802FF
External DAC 0x00680000-0x006800FF
PRMCIO 0x00601000-0x00601FFF VGA emulation - CRTC + attribute controller
PRAMDAC 0x00680300-0x00680FFF (used for color lookup tables, hardware cursor, video overlay, PLL for clocking and pixel generation)
"USER" DAC 0x00681200-0x00681FFF
DMA submission w/FIFO 0x00800000-0x00FFFFFF NV_USER - uses FIFO buffer
PNVM / PDFB / VRAM 0x01000000-0x017FFFFF (actual VRAM amount depends on card, but max is 8MB)
RAMIN 0x01C00000-0x01FFFFFF (note that this is actually mapped in the last 1mb of vram)
contains ramht that has obj parameters for submission, configurable

View File

@@ -0,0 +1,54 @@
NV3 DMA Engine
(DirectDraw Driver)
Initially set CACHES, CACHE1_PULL0, CACHE1_PULL1, CACHE1_DMA0 to 1
Same for other areas
CACHE1_PUSH1 contains CHID
If it's different:
If RmFifoFlushContext failed: Do nothing
Set PULL0, PUSH0, Caches to 1, return false
If it's not:
DMA TLB PTE seems to be 1 for direct programming, maybe RM does it differently
Tag=FFFFFFFF
CACHE1_DMA1 - Number of bytes to send
CACHE1_DMA2 - Get offset
CACHE1_DMA3 - Bus address space (Area BAR0 mapped to? Or bar1?)
TO START:
To set up DMA for for Cache1 Puller: CACHE1_PULL0 -> 1, changes to 0 when done
To set up DMA Cache1 Push: CACHE1_PUsh0 -> 1, changes to 0 when done
Set CACHES to 1
GO: Set DMA0 to 1
***** Implementation in Driver ******
You can dma to "localvidmem:", "sysvidmem:" or "sysmem:", this is represented by a driver
CAUSE OF FAILURE:
the pfifo is never free because it never processes the submitted objects
which means that the FIFO is never free
which means that the drivers spin forever waiting for the fifo to be free
DMA_OBJECT STRUCTURE IN RESOURCE MANAGER:
0x328: Valid
0x34c: base address?
0x374: actually do the transfer
some objects don't actually need to do dma, for example, video patchcord, it just creates a structure in the driver, and rop just allocate ssome memory to put the data for the patchcord/rop thing
dma start=nv3_mini dc67
call of mthdCreate for ***DRIVER*** CLASS ID: a7b44, check ptr

View File

@@ -0,0 +1,29 @@
NV_PFB_CONFIG_0
Observed Valus:
Drivers 0x1000
BIOS 0x1114
Bits
5:0 Resolution
9:8 Pixel depth
12:12 Tiling
13:13 Tiling Debug
14:14 Tiling Debug Tile Size
17:15 "Tetris" tiling
19:18 "Tetris" tiling shift
22:20 Bank Swap
23 Unused
NV_PFB_CONFIG_1
2:0 CAS Latency
3:3 NEC Mode (PC-98?)
7:4 RAS Default / 9 Cycles
10:8 RAS PCHG
14:12 RAS Low
18:16 MRS to RAS
22:20 Write to Read
26:24 RAS to CAS
30:28 Read to Write
31:31 Read to PCFg

View File

@@ -0,0 +1,38 @@
PIO VS dma
driver is DIRECTLY modifying pfifo
8X8 channel setup
Names are 32-bit integers >4096
RAMFC - DMA Context object 0,0 to 8,8
context = channel, render object, object type, offset in instance memory
for a rectangle (type 0x47), object render = 1, channel 0, at 0x0400 in the ramht memory
=0x00c70400 as the context
the ramht hash :
xor every byte of the hash individually and then xor that with the channel number
so obj id 01020304 in channel 0 is 1 xor 2 xor 3 xor 4 xor 0
Store in RAMHT at <subchannel start within RAMHT> + 4*16 = name
Store in RAMHT at <subchannel start within RAMHT> + 4*16 + 4 = context
then the ramin stuff starts at *0xc04000 since c00000 is the start of ramin [PCI BAR1] which is where you put the contents of the class struct
nv_user
Consider the 8x8 channels as 64 subchannels.
Now you can do:
They seem to end at 0x880000
(0x880000)/64 = 0x2000 for each channel
FAILURE -> RAMRO!

View File

@@ -0,0 +1,26 @@
RIVA fansites (?????):
RIVA User's Group http://tiger.tnstate.edu:8080/
RIVA 128 Homepage http://pages.prodigy.net/babblin5/Main.html -> Riva3D (riva3d.com)
Zone 128 http://www.tc.umn.edu/~reda0003/zone128/
RIVAZone https://web.archive.org/web/19981212032348/http://www.rivazone.com/ (Launched January 2, 1998)
Dimension 128 http://dimension128.smartcom.net (early domain name that was mostly not archived) -> d128.com (1999-2001)
Riva3D https://web.archive.org/web/20000525110305/http://riva3d.gxnetwork.com/s3.html
nVNews https://web.archive.org/web/20001205171202/http://www.nvnews.net/ (1999-2015)
BluesNews has stuff https://www.bluesnews.com/archives/
(July 1996-present!)
https://www.bluesnews.com/archives/july97-3.html July 21, 1997 ("unreleased nvidia RIVA chipset is indeed faster than 3dfx" (carmack)
"However, using the nvidia RIVA 128 chip it runs 1 f/s faster"
https://web.archive.org/web/19980615024744/http://www.ogr.com/columns/techtalk/technology_talk_0611_2.shtml First mention of NV10 (June 1998)
"RIVA 128 Turbo" early ZX name
"Riva4" early TNT name
https://web.archive.org/web/20001002193706/http://www.rivazone.com/files/rivalog.txt
https://web.archive.org/web/20010422044820/http://www.rivazone.com/finger/finger.cgi?nick@finger.nvidia.com nvidia .plan files
WAVE Report - april 1997 date for tapeout

View File

@@ -0,0 +1,8 @@
Name Base Version Notes Date API Support Platform
0.75_nt4 Version 0.75 No Resource Manager (miniport) 1997-08-15 GDI NT4.0
0.77_win9x Version 0.77 Symbols (COFF/VXD) 1997-09-02 GDI, D3D5
nv3quake.zip Version 1.21 OpenGL Alpha 1 1997-11-14 GDI, D3D5, OpenGL 1.1 (alpha; Build 151) Win9x
quakea2f.zip Version 1.21 OpenGL Alpha 2 1997-12-02 GDI, D3D5, OpenGL 1.1 (alpha; Build 258) Win9x
win95_131.zip Version 1.31 OpenGL Beta 1 1998-02-04 GDI, D3D5, OpenGL 1.1 (beta; Build 661) Win9x

View File

@@ -0,0 +1,63 @@
GPU companies (the period they made gpus)
DEC 19xx-1998
HP 19xx-2000+ (possibly until present)
IBM 19xx-2002+ (possibly until present)
E&S 1968-2006
Intergraph 1969-2000
Apple 1976-present (some break)
Motorola 1977-1994+ (???)
TI 1979-1988+
Matrox 1979-2014
Hitachi 1981-1986
SGI 1981-2009
Intel 1983-present
Number Nine 1983-2000
Tseng Labs 1983-1997
Cirrus Logic 1984-1998
Video 7 1985-1991
C&T 1985-1999
Imagination 1985-present
NCR 1986-1993
Paradise/WD 1986-1996
Gemini 1987-1990
Genoa Systems 1987-1991
Trident 1987-2003
Oak Technology 1988-1997
Realtek 1988-1997
Compaq 1989-1991
Sun 1989-2002(+?)
S3 Graphics 1989-2010
Macronix 1989-1998
Winbond 1989-1996+
Tamarack 198x-1991+
UMC 198x-1993
Sigma Designs 198x-1996
Acer 198x-1998 (roughly)
Fujitsu 198x-1998
AMD 1991-present (really starting in 2006)
HMC 1991-1994
Avance Logic 1991-1995
Weitek <1991-1996
Bitboys 1991-2006
IIT 1992-1994
Weitek 1992-1996(?)
Rendition 1993-1998
ARK Logic 1993-1999
S-MOS/Stellar 1994-1999
Nvidia 1993-present
Alliance 1994-1997
iGS 1994-1999
3dfx 1994-2000
3dlabs 1994-2006
Silicon Motion 1995-200?
SiS 1995-2007
Dynamic Pictures1996-1997
NeoMagic 1996-2000
GigaPixel 1997-2000
Philips 1997-199x
Tatung 199x-199x
ASPEED 2004-present
Jingjia 2006-present

View File

@@ -0,0 +1,21 @@
NV3/NV3T/NV4 hardware cursor
Unlock extended CRTC registers
CIO_CRE_HCUR_ADDR0
Bits [6:0] = Address
CIO_CRE_HCUR_ADDR1
Bits [7:3] = Bits [11:7] of address
Bit 1 = Cursor Doubling
Bit 0 = Enable
PRAMDAC_CU_START_POS (MMIO 0x680300)
Bits [11:0] = X Pos
Bits [27:16] = Y Pos
CursorAddress >> 16 written to addr0
(((CursorAddress >> 11) & 0x1F) << 3) | 1 (for enable) written to addr1
Lock extended CRTC registers
Enable - write

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

View File

@@ -0,0 +1,112 @@
Driver version: Windows 2000 Version 3.37 (23 March 1999) - SDK: Windows 2000 build 1996.1
Features:
Resource Manager Yes
DirectDraw Yes
OpenGL Yes
Direct3D No
get started:
MUST USE X86 WINDBG
Does older windbg support coff???
sxe ld nv3_mini.sys
bp nv3_mini + 0x409ac - 0x10000 (nv3_mini+0x309ac)
offset purpose
30a1a RmInitRm call
6be7 initClientInfo call
6878 initClientInfo
6bec Check for initClientInfo success
6bf4 initGrPatchPool call
6bf9 Check for initGrPatchPool success
6c01 initDmaListElementPool call
6c06 Check for initDmaListElementPool
6c1c initDisplayInfo call
6c26 rmInitRm End
Success: eax=FFFFFFFFh / -1
6c26 Fail eax=0
30c8b NvFindAdapter
30cb6 NvFindAdapter -> NVIsPresent call
1010 NVIsPresent function
102f NVIsPresent VideoPortGetAccessRanges call
103b NVIsPresent VideoPortGetAccessRanges call success check (only possible way to fail)
127c NVIsPresent end
30cbb NvFindAdapter -> NVIsPresent success check
Success: al=1
Failure: al=0
30cca NVIsPresent NVMapMemoryRanges call
e9e NVIsPresent NVMapMemoryRanges VideoPortGetDeviceBase call #1 [PCI Space]
ea4 NVIsPresent NVMapMemoryRanges VideoPortGetDeviceBase call #1 success check [PCI Space]
ebd NVIsPresent NVMapMemoryRanges VideoPortFreeDeviceBase [conditional]
ec3 NVIsPresent NVMapMemoryRanges VideoPortFreeDeviceBase [conditional] success check
ed6 NVIsPresent NVMapMemoryRanges VideoPortGetDeviceBase call #2 [MMIO]
edc NVIsPresent NVMapMemoryRanges VideoPortGetDeviceBase call #2 success check [MMIO]
f0c NVIsPresent NVMapMemoryRanges VideoPortGetDeviceBase call #3 [LFB/RAMIN?]
f12 NVIsPresent NVMapMemoryRanges VideoPortGetDeviceBase call #3 success check [LFB/RAMIN?]
30ccf NvFindAdapter NVMapMemoryRanges success check
Success: eax=0
Failure: eax=87
30cf1 NvFindAdapter RmInitNvMapping call
6ce6 NvFindAdapter RmInitNvMapping
30cf6 NVIsPresent RmInitNvMapping success check
Success: eax!=0 (in practice 0xFFFFFFFF/-1)
Failure: eax=0
30d5c NvFindAdapter
30d64 NvFindAdapter RmPostNvDevice call
6d88 RmPostNvDevice function
6d91 NvFindAdapter DevinitInitializeDevice call
6d96 NvFindAdapter DevinitInitializeDevice success check
Success: eax=0 (?)
Failure: eax=1
e546 DevinitInitializeDevice function
[very complicated]
[several register reads]
e61d DevinitPrepDeviceForInit call
e641 DevinitPrepDeviceForInit function
e627 InitNV call
e67a InitNV function
30d64 NVIsPresent RmPostNvDevice success check
Success: eax=0 (?)
Failure: eax=1
30d78 NVIsPresent NVGetNVInfo call
30d7d NVIsPresent NVGetNVInfo success check
3e9a NvFindAdapter end
Success: eax=0
Fail eax=55 (RmInitNvMapping or NVIsPresent failed)
Fail eax=87 (NVMapMemoryRanges or NVMapFrameBuffer failed)
30ea3 NVInitialize
30f02 NVStartIO
2aa6 NVInterrupt
30a2d NVGetChildDescriptor
30a9c NVGetPowerState
30b20 NVSetPowerState
Driver Init Status:
DriverEntry Success
rmInitRm -> initClientInfo Success
rmInitRm -> initGrPatchPool Success
rmInitRm -> initDmaListElementPool Success
rmInitRm -> initDisplayInfo Success
rmInitRm overall Success
NvFindAdapter Success 17:32 27/11/2024
NvFindAdapter -> NvIsPresent Success 16:19 24/11/2024
NvFindAdapter -> NvMapMemoryRanges Success 19:15 26/11/2024
NvFindAdapter -> RmInitNvMapping Success 19:18 26/11/2024
NvFindAdapter -> RmPostNvDevice Success 17:32 27/11/2024
NvFindAdapter -> NVGetNVInfo Success 17:32 27/11/2024
NvFindAdapter -> NVMapFrameBuffer Success 17:32 27/11/2024
00:00 30/12/2024: stateGr -> i2c_Write
22:31 31/12/2024: fifoService
fix ptimer issue

View File

@@ -0,0 +1,90 @@
nv3_disp:
0x10fe -> DrvBitBlt
0x19be -> DrvCopyBits
0x597c -> DrvCreateDeviceBitmap
0x5a6e -> DrvDeleteDeviceBitmap
0x60b8 -> DrvTextOut
0x6248 -> DrvDestroyFont
0x64b8 -> DrvRealizeBrush
0x6a46 -> DrvDitherColor
0x797a -> DrvGetDirectDrawInfo
0x7b14 -> DrvEnableDirectDraw
0x7b70 -> DrvDisableDirectDraw
0x817c -> DrvPaint
0x81c2 -> DrvResetPDEV
0x82dc -> DrvEnableDriver
0x8312 -> DrvEnablePDEV
0x83ee -> DrvDisablePDEV
0x840a -> DrvCompletePDEV
0x8418 -> DrvSynchronise
0x845a -> DrvEnableSurface
0x851a -> DrvDisableSurface
0x8554 -> DrvAssertMode
0x8690 -> DrvGetModes
0xe59a -> DrvEscape
0xf3ee -> DrvFillPath
0xf3f6 -> DrvStrokePath
0xfa08 -> DrvLineTo
0x12fee -> DrvSetPalette
0x132a4 -> DrvMovePointer
0x13d20 -> DrvSetPointerShape
0x13dea -> DrvStretchBlt
0x147f2 -> DrvSetPixelFormat
0x1483c -> DrvDescribePixelFormat
0x1495a -> DrvClipChanged
0x255b8 -> DrvSwapBuffers
DrvEnableDriver SUCCESS
DrvEnablePDEV SUCCESS 23:28 09/02/2025
Check for cjCaps >= 0x130 && cjDevInfo >= 0x12C SUCCESS 23:31 09/02/2025
EngAllocMem call SUCCESS 23:38 09/02/2025
CreateOglGlobalMemory call SUCCESS 23:40 09/02/2025
bInitializeModeFields call SUCCESS 23:41 09/02/2025
bInitializePalette call SUCCESS 23:42 09/02/2025
EngDeviceIoControl IOCTL 0x232020 (CHECK mini) SUCCESS
EngDeviceIoControl IOCTL 0x232044 (CHECK mini) SUCCESS (eax=0)
DrvCompletePDEV SUCCESS 23:52 09/02/2025
DrvEnableSurface
bEnableHardware call SUCCESS 22:36 13/02/2025
EngCreateSemaphore call #1 csCrtc SUCCESS 00:55 10/02/2025
EngCreateSemaphore call #2 csFifo SUCCESS 00:57 10/02/2025
EngDeviceIoControl IOCTL 0x230460 SUCCESS 00:57 10/02/2025
EngDeviceIoControl IOCTL 0x230458 SUCCESS 00:57 10/02/2025
NvAllocRoot SUCCESS 01:03 10/02/2025
NvAllocDevice SUCCESS 01:04 10/02/2025
NV3/NV4 architecture check SUCCESS 01:14 10/02/2025
bAssertModeHardware call (bEnable=1) SUCCESS Passing starting with build at 02:23 10/02/2025
EngDeviceIoControl IOCTL 0x23040C] SUCCESS Passing starting with build at 02:23 10/02/2025
nv3_mini NVStartIO ioctlcode=0x23040C
NVSetMode
NV3SetMode SUCCESS 01:53 10/02/2025
RmUnloadState SUCCESS 01:48 10/02/2025
VBESetModeEx SUCCESS 01:51 10/02/2025
NV_OEMEnableExtensions SUCCESS 01:52 10/02/2025
UpdateArbitrationSettings SUCCESS 01:52 10/02/2025
RmLoadState SUCCESS 01:53 10/02/2025
NV3EnableCursor SUCCESS 01:54 10/02/2025
NV3WaitUntilFinished SUCCESS 02:23 10/02/2025
EngDeviceIoControl IOCTL 0x230408 SUCCESS 02:26 10/02/2025
EngDeviceIoControl IOCTL 0x232024 SUCCESS 02:26 10/02/2025
NvAllocHardware SUCCESS 02:29 10/02/2025
bCreateStdPatches(?) SUCCESS (EAX=1!!!) 22:24 13/02/2025
CHECK - NV4 N/A
vDestroyStdPatches(?) N/A
NV3_WaitForOneVerticalRefresh SUCCESS
EngDeviceIoControl IOCTL 0x230410 SUCCESS
SET UP CORRECT FUNCTION POINTERS
Indirect call (call dword [edi]) to NV3_WaitWhileGraphicsEngineBusy HANG 22:14 16/02/2025
_heap_init call
bEnableOffscreenHeap call
bEnablePointer call
bEnableText call
bEnablePalette call
bEnableDirectDraw call
EngCreateBitmap call
EngAssociateBitmap call
DrvDisableSurface: ONLY IN THE CASE OF FAILURE

View File

@@ -0,0 +1,37 @@
Object classes as understood by the GPU.
(May be represented, in RAMFC, as 0x40+ 22:16)
0x00 = Invalid
0x01 = beta factor
0x02 = ROP5 operation
0x03 = Chroma key
0x04 = Plane mask
0x05 = Clipping rectangle
0x06 = Pattern
0x07 = Rectangle
0x08 = Point
0x09 = Line
0x0A = Lin (line without starting or ending pixel)
0x0B = Triangle
0x0C = Windows 95 GDI text acceleration
0x0D = Memory to memory format
0x0E = Scaled image from memory
0x0F = INVALID
0x10 = Blit
0x11 = Image
0x12 = Bitmap
0x13 = INVALID
0x14 = Transfer to Memory
0x15 = Stretched image from CPU
0x16 = INVALID
0x17 = Direct3D 5.0 accelerated textured triangle w/zeta buffer
0x18 = Point w/zeta buffer
0x19 = INVALID
0x1A = INVALID
0x1B = INVALID
0x1C = Image in memory
0x1D = INVALID
0x1E = INVALID
0x1F = INVALID

View File

@@ -0,0 +1,161 @@
12=unk_int12
16=unk_short16
22=unk_short22 (seems to determine if nv_sys_ptr is valid)
24=unk_short24
26=unk_short26
44=unk_int44
56=unk_int56
60=unk_int60
64=unk_short64
66=unk_short66
82=unk_short82
362..822:
big_struct
(weird alignment?)
477
489
505
1140=nv_sys_ptr
<very big structure - add when we add this structure>
1156=unk_int1156 [start of structure]
1160=unk_int1160
1168=unk_int1168 [possibly a byte array of up to 16 bytes)
1184=unk_int1184
1208=unk_int1208
1212=unk_int1212
1216=unk_int1216
1220=unk_int1220
1224=unk_int1224
1236=unk_int1236
1240=unk_int1240
1244=unk_int1244
1248=unk_int1248
1256=unk_int1256
1260=fog_table_enable
1264=unk_int1264
1270=unk_int1270
1272=unk_byte1272
1273=unk_byte1273
1274=unk_byte1274 (?)
1275=unk_byte1275
1316=unk_int1316
1320=unk_int1320
1332=unk_int1332
1340=unk_int1340
1344=unk_int1344
1356=unk_int1356
1360=d3d_clear_enabled
1364=texture_enabled
1368=mipmap_size_max
1372=mipmap_levels
1376=user_mipmaps
1380=zoh_mode (bool)
1384=tex_heap
1388=text_size
1392=video_texture
1396=min_video_tex_size
1400=draw_prim
1404=spread_x
1408=spread_y
1412=size_adj
1416=turbo_adj
1420=dma_min_push_count
1424=dma_push_enable
1436=unk_int1436
1440=unk_int1440
1448=unk_int1448
1452=unk_int1452 (set to value of unk_int1908)
1456=unk_int1456 (set to value of unk_int1956)
1460=unk_int1460 (set to value of unk_int2020)
1516=unk_int1516
1520=unk_int1520
1524=unk_int1524
1528=unk_int1528
1532=unk_int1532
1548=unk_int1548
1552=unk_int1552
1556=unk_int1556
1560=unk_int1560
1564=unk_int1564
1600=unk_int1600
1612=unk_int1612
1616=unk_int1616
1620=unk_int1620
1632=unk_int1632
1640=unk_int1640
1644=unk_ptr1644
1676=unk_int1676
1680=unk_int1680
1684=unk_int1684
1688=unk_int1688
1692=unk_int1692
1696=unk_int1696
1700=unk_int1700
1716=unk_int1716
1720=unk_int1720
1724=unk_int1724
1728=unk_ptr1728
1848=unk_int1848
1852=unk_int1852
1856=unk_int1856 (unk_int1552 | 0x800)
1864=unk_func_ptr1864
1868=unk_int1868
1884=unk_int1884
1888=ptr_to_start_of_structure?
1892=hInstDll
1896=unk_int1896
1900=unk_int1900
1908=unk_int1908
1912=unk_int1912
1916=unk_func_ptr1916
1920=unk_func_ptr1920
1932=unk_func_ptr1932
1936=unk_func_ptr1936
1944=unk_func_ptr1944
1956..2020: Set of function pointrs
1956=unk_int1956
1960=unk_int1960
1960=unk_func_ptr1960
1964=unk_func_ptr1964
1968=unk_func_ptr1968
1976=unk_func_ptr1976
1980=unk_func_ptr1980
1988=unk_func_ptr1988
1996=unk_func_ptr1996
2000=unk_func_ptr2000
2004=unk_func_ptr2004
2008=unk_func_ptr2008
2020=unk_int2020
2024=unk_int2024
2028=unk_int2028
2032=unk_int2032

View File

@@ -0,0 +1,9 @@
old nouveau wiki:
real VRAM address = VRAM_size - (ramin_address - (ramin_address % reversal_unit_size)) - reversal_unit_size + (ramin_address % reversal_unit_size)
nv3=16 bytes
0x400000 - ((0x100000) - (0x100000 % 16)) - 16 + (0x100000 % 16) = 2ffff0

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,64 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* NV3 headers for rendering
*
*
*
* Authors: Connor Hyde, <mario64crashed@gmail.com> I need a better email address ;^)
*
* Copyright 2024-2025 Connor Hyde
*/
#pragma once
/* Core */
void nv3_render_current_bpp();
void nv3_render_current_bpp_dfb_8(uint32_t address);
void nv3_render_current_bpp_dfb_16(uint32_t address);
void nv3_render_current_bpp_dfb_32(uint32_t address);
/* Pixel */
void nv3_render_write_pixel(nv3_coord_16_t position, uint32_t color, nv3_grobj_t grobj);
uint8_t nv3_render_read_pixel_8(nv3_coord_16_t position, nv3_grobj_t grobj);
uint16_t nv3_render_read_pixel_16(nv3_coord_16_t position, nv3_grobj_t grobj);
uint32_t nv3_render_read_pixel_32(nv3_coord_16_t position, nv3_grobj_t grobj);
/* Address */
uint32_t nv3_render_get_vram_address(nv3_coord_16_t position, nv3_grobj_t grobj);
uint32_t nv3_render_get_vram_address_for_buffer(nv3_coord_16_t position, uint32_t buffer);
/* Colour Conversion */
uint32_t nv3_render_get_palette_index(uint8_t index); // Get a colour for a palette index. (The colours are 24 bit RGB888 with a 0xFF alpha added for some purposes.)
uint32_t nv3_render_to_chroma(nv3_color_expanded_t expanded); // Convert a colour to A1R10G10B10 for chroma key purposes.
nv3_color_expanded_t nv3_render_expand_color(uint32_t color, nv3_grobj_t grobj); // Convert a colour to full RGB10 format from the current working format.
uint32_t nv3_render_downconvert_color(nv3_grobj_t grobj, nv3_color_expanded_t color); // Convert a colour from the current working format to RGB10 format.
/* ROP */
uint8_t nv3_render_translate_nvrop(nv3_grobj_t grobj, uint32_t rop);
/* Pattern */
void nv3_render_set_pattern_color(nv3_color_expanded_t pattern_colour, bool use_color1);
/* Primitives */
void nv3_render_rect(nv3_coord_16_t position, nv3_coord_16_t size, uint32_t color, nv3_grobj_t grobj); // Render an A (unclipped) GDI rect
void nv3_render_rect_clipped(nv3_clip_16_t clip, uint32_t color, nv3_grobj_t grobj); // Render a B (clipped) GDI rect.
/* Chroma */
bool nv3_render_chroma_test(uint32_t color, nv3_grobj_t grobj);
/* Blit */
void nv3_render_blit_image(uint32_t color, nv3_grobj_t grobj);
void nv3_render_blit_screen2screen(nv3_grobj_t grobj);
/* GDI */
void nv3_render_gdi_transparent_bitmap(bool clip, uint32_t color, uint32_t bitmap_data, nv3_grobj_t grobj);
void nv3_render_gdi_1bpp_bitmap(uint32_t color0, uint32_t color1, uint32_t bitmap_data, nv3_grobj_t grobj); /* GDI Type-E: Clipped 1bpp colour-expanded bitmap */
/* DMA */
void nv3_perform_dma_m2mf(nv3_grobj_t grobj);

View File

@@ -0,0 +1,178 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* The real Nvidia driver.
* Implements components shared by all variants of the Nvidia GPUs (hopefully to be) supported by 86Box.
*
* This driver draws on collaborative work by many people over many years.
*
* Credit to:
*
* - Marcelina Kościelnicka (envytools) https://envytools.readthedocs.io/en/latest/
* - fuel (PCBox developer) https://github.com/PCBox/PCBox
* - nouveau developers https://nouveau.freedesktop.org/
* - Utah GLX developers https://utah-glx.sourceforge.net/
* - XFree86 developers https://www.xfree86.org/
* - xemu developers https://github.com/xemu-project/xemu
* - RivaTV developers https://rivatv.sourceforge.net (esp. https://rivatv.sourceforge.net/stuff/riva128.txt)
* - Nvidia for leaking their driver symbols numerous times ;^) https://nvidia.com
* - Vort (Bochs GeForce fork) https://github.com/Vort/Bochs/tree/geforce
* - People who prevented me from giving up (various)
*
* Authors: Connor Hyde / starfrost <mario64crashed@gmail.com>
*
* Copyright 2024-2025 Connor Hyde
*/
#ifdef EMU_DEVICE_H // what
//TODO: split this all into nv1, nv3, nv4...
#include <86box/log.h>
#include <86box/i2c.h>
#include <86box/vid_ddc.h>
#include <86box/timer.h>
#include <86box/vid_svga.h>
#include <86box/vid_svga_render.h>
#include <86box/nv/vid_nv_rivatimer.h>
void nv_log_set_device(void* device);
void nv_log(const char *fmt, ...);
// Verbose logging level.
void nv_log_verbose_only(const char *fmt, ...);
// Defines common to all NV chip architectural generations
// PCI IDs
#define PCI_VENDOR_NV 0x10DE // NVidia PCI ID
#define PCI_VENDOR_SGS 0x104A // SGS-Thompson
#define PCI_VENDOR_SGS_NV 0x12D2 // SGS-Thompson/NVidia joint venture
#define NV_PCI_NUM_CFG_REGS 256 // number of pci config registers
// 0x0000 was probably the NV0 'Nvidia Hardware Simulator'
#define NV_PCI_DEVICE_NV1 0x0008 // Nvidia NV1
#define NV_PCI_DEVICE_NV1_VGA 0x0009 // Nvidia NV1 VGA core
#define NV_PCI_DEVICE_NV2 0x0010 // Nvidia NV2 / Mutara V08 (cancelled)
#define NV_PCI_DEVICE_NV3 0x0018 // Nvidia NV3 (Riva 128)
#define NV_PCI_DEVICE_NV3T 0x0019 // Nvidia NV3T (Riva 128 ZX)
#define NV_PCI_DEVICE_NV4 0x0020 // Nvidia NV4 (RIVA TNT)
#define NV_CHIP_REVISION_NV1_A0 0x0000 // 1994
#define NV_CHIP_REVISION_NV1_B0 0x0010 // 1995
#define NV_CHIP_REVISION_NV1_C0 0x0020 // 1995-96?
#define NV_CHIP_REVISION_NV3_A0 0x0000 // January 1997
#define NV_CHIP_REVISION_NV3_B0 0x0010 // October 1997
#define NV_CHIP_REVISION_NV3_C0 0x0020 // 1998
// Architecture IDs
#define NV_ARCHITECTURE_NV1 1 // NV1/STG2000
#define NV_ARCHITECTURE_NV2 2 // Nvidia 'Mutara V08'
#define NV_ARCHITECTURE_NV3 3 // Riva 128
#define NV_ARCHITECTURE_NV4 4 // Riva TNT and later
#define NV_MAX_BUF_SIZE_X 1920 // Maximum buffer size, X
#define NV_MAX_BUF_SIZE_Y 1200 // Maximum buffer size, Y
typedef enum nv_bus_generation_e
{
// NV1 - Prototype version
nv_bus_vlb = 0,
// NV1
// NV3
nv_bus_pci = 1,
// NV3
nv_bus_agp_1x = 2,
// NV3T
// NV4
nv_bus_agp_2x = 3,
} nv_bus_generation;
// PCI configuration
typedef struct nv_pci_config_s
{
uint8_t pci_regs[NV_PCI_NUM_CFG_REGS]; // The actual pci register values (not really used, just so they can be stored - they aren't very good for code readability)
bool vbios_enabled; // is the vbios enabled?
uint8_t int_line;
} nv_pci_config_t;
// NV Base
typedef struct nv_base_s
{
rom_t vbios; // NVIDIA/OEm VBIOS
nv_pci_config_t pci_config; // PCI configuration
// move to nv3_cio_t?
svga_t svga; // SVGA core (separate to nv3) - Weitek licensed
uint32_t vram_amount; // The amount of VRAM
void* log; // new logging engine
// stuff that doesn't fit in the svga structure
uint32_t cio_read_bank; // SVGA read bank
uint32_t cio_write_bank; // SVGA write bank
mem_mapping_t framebuffer_mapping; // Linear Framebuffer / NV_USER memory mapping
mem_mapping_t mmio_mapping; // mmio mapping (32MB unified MMIO)
mem_mapping_t framebuffer_mapping_mirror; // Mirror of LFB mapping
mem_mapping_t ramin_mapping; // RAM INput area mapping
mem_mapping_t ramin_mapping_mirror; // RAM INput area mapping (mirrored) - NV3 ONLY
uint8_t pci_slot; // pci slot number
uint8_t pci_irq_state; // current PCI irq state
uint32_t bar0_mmio_base; // PCI Base Address Register 0 - MMIO Base
uint32_t bar1_lfb_base; // PCI Base Address Register 1 - Linear Framebuffer (NV_BASE)
nv_bus_generation bus_generation; // current bus (see nv_bus_generation documentation)
uint32_t gpu_revision; // GPU Stepping
double pixel_clock_frequency; // Frequency used for pixel clock#
double refresh_time; // Rough estimation of refresh rate, for when we can present the screen
double refresh_clock; // Time since the last refresh
rivatimer_t* pixel_clock_timer; // Timer for measuring pixel clock
bool pixel_clock_enabled; // Pixel Clock Enabled - stupid crap used to prevent us enabling the timer multiple times
double memory_clock_frequency; // Source Frequency for PTIMER
rivatimer_t* memory_clock_timer; // Timer for measuring memory/gpu clock
// VCLK / NVCLK do not have timers set here.
pc_timer_t* nv4_vclk_timer; // NV4+ MCLK (Video RAM) timer
bool memory_clock_enabled; // Memory Clock Enabled - stupid crap used to prevent us eanbling the timer multiple times
void* i2c; // I2C for monitor EDID
void* ddc; // Display Data Channel for EDID
bool agp_enabled; // AGP Enabled (for debugging)
bool agp_sba_enabled; // AGP Sideband Addressing enabled
//
// DEBUG UI STUFF
//
bool debug_dba_enabled; // Debug DBA override
uint32_t debug_dba; // Debug DBA
} nv_base_t;
#define NV_REG_LIST_END 0xD15EA5E
// The NV architectures are very complex.
// There are hundreds of registers at minimum, and implementing these in a standard way would lead to
// unbelievably large switch statements and horrifically unreadable code.
// So this is used to abstract it and allow for more readable code.
// This is mostly just used for logging and stuff.
// Optionally, you can provide a function that is run when you read to and write from the register.
// You can also implement this functionality in a traditional way such as a switch statement, for simpler registers. To do this, simply set both read and write functions to NULL.
// Typically, unless they are for a special purpose (and handled specially) e.g. vga all register reads and writes are also 32-bit aligned
typedef struct nv_register_s
{
int32_t address; // MMIO Address
char* friendly_name; // Friendly name
// reg_ptr not needed as a parameter, because we implicitly know which register si being tiwddled
uint32_t (*on_read)(void); // Optional on-read function
void (*on_write)(uint32_t value); // Optional on-write fucntion
} nv_register_t;
nv_register_t* nv_get_register(uint32_t address, nv_register_t* register_list, uint32_t num_regs);
#endif

View File

@@ -0,0 +1,167 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* Quadratic Heaven
*
* Authors: Connor Hyde <mario64crashed@gmail.com>
*
* Copyright 2024-2025 Connor Hyde
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
extern const device_config_t nv1_config[]; // Config for RIVA 128 (revision A/B)
//
// PCI Bus Information
//
#define NV1_PCI_BAR0_SIZE 0xFFFFFF
//
// VRAM
//
#define NV1_VRAM_SIZE_1MB 0x100000
#define NV1_VRAM_SIZE_2MB 0x200000
#define NV1_VRAM_SIZE_4MB 0x400000
// Video BIOS
#define NV1_VBIOS_E3D_2X00 "roms/video/nvidia/nv1/Diamond_Edge_3D_2x00.BIN"
#define NV1_VBIOS_E3D_3X00 "roms/video/nvidia/nv1/Diamond_Edge_3D_3400_BIOS_M27C256.BIN"
//
// PMC
//
#define NV1_PMC_BOOT_0 0x0
#define NV1_PMC_BOOT_0_REVISION 0
#define NV1_PMC_BOOT_0_REVISION_A 0x0 // Prototype (1994)
#define NV1_PMC_BOOT_0_REVISION_B1 0x0 // Prototype (early 1995)
#define NV1_PMC_BOOT_0_REVISION_B2 0x0 // Prototype (mid 1995)
#define NV1_PMC_BOOT_0_REVISION_B3 0x0 // Final?
#define NV1_PMC_BOOT_0_REVISION_C 0x0 // Final?
#define NV1_PMC_BOOT_0_IMPLEMENTATION 8
#define NV1_PMC_BOOT_0_IMPLEMENTATION_NV0 0x1 // Nvidia Hardware Simulator (1993-1994)
#define NV1_PMC_BOOT_0_IMPLEMENTATION_NV1_D32 0x2 // NV1 + DRAM + SGS-Thomson STG-1732/1764 DAC
#define NV1_PMC_BOOT_0_IMPLEMENTATION_NV1_V32 0x3 // NV1 + VRAM + SGS-Thomson STG-1732/1764 DAC
#define NV1_PMC_BOOT_0_IMPLEMENTATION_PICASSO 0x4 // NV1 + VRAM + NV 128-bit DAC
// Defines the NV architecture version (NV1/NV2/...)
#define NV1_PMC_BOOT_0_ARCHITECTURE 16
#define NV1_PMC_BOOT_0_ARCHITECTURE_NV0 0x0 // Nvidia Hardware Simulator (1993-1994)
#define NV1_PMC_BOOT_0_ARCHITECTURE_NV1 0x1 // NV1 (1995)
#define NV1_PMC_BOOT_0_ARCHITECTURE_NV2 0x2 // Mutara (1996, cancelled)
#define NV1_PMC_DEBUG_0 0x80
#define NV1_PMC_INTR_0 0x100
#define NV1_PMC_INTR_EN_0 0x140
#define NV1_PMC_INTR_EN_0_INTA 0
#define NV1_PMC_INTR_EN_0_INTB 4
#define NV1_PMC_INTR_EN_0_INTC 8
#define NV1_PMC_INTR_EN_0_INTD 12
#define NV1_PMC_INTR_EN_0_DISABLED 0x0
#define NV1_PMC_INTR_EN_0_HARDWARE 0x1
#define NV1_PMC_INTR_EN_0_SOFTWARE 0x2
#define NV1_PMC_INTR_EN_0_ALL 0x3 // (HARDWARE | SOFTWARE)
#define NV1_PMC_INTR_READ 0x160
//TODO: DEFINE bits
#define NV1_PMC_ENABLE 0x200
//
// PRAMIN
//
#define NV1_RAMIN_START 0x100000
//
// PAUTH
// Scary nvidia mode
//
// Read only
#define NV1_PAUTH_DEBUG_0 0x605080
#define NV1_PAUTH_DEBUG_0_BREACH_DETECTED 0
#define NV1_PAUTH_DEBUG_0_EEPROM_INVALID 4
#define NV1_PAUTH_CHIP_TOKEN_0 0x605400
#define NV1_PAUTH_CHIP_TOKEN_1 0x605404
#define NV1_PAUTH_PASSWORD_0(i) 0x605800+(i*16)
#define NV1_PAUTH_PASSWORD_1(i) 0x605804+(i*16)
#define NV1_PAUTH_PASSWORD_2(i) 0x605808+(i*16)
#define NV1_PAUTH_PASSWORD_3(i) 0x60580C+(i*16)
#define NV1_PAUTH_PASSWORD_SIZE 128
//
// PFB
//
#define NV1_PFB_BOOT_0 0x600000
#define NV1_PFB_BOOT_0_RAM_AMOUNT 0
#define NV1_PFB_BOOT_0_RAM_AMOUNT_1MB 0x0
#define NV1_PFB_BOOT_0_RAM_AMOUNT_2MB 0x1
#define NV1_PFB_BOOT_0_RAM_AMOUNT_4MB 0x2
//
// PEXTDEV
//
#define NV1_STRAPS 0x608000
#define NV1_STRAPS_STRAP_VENDOR 0
//
// PRAM+RAMIN
//
#define NV1_PRAM_CONFIG 0x602200
#define NV1_PRAM_CONFIG_SIZE 0
#define NV1_PRAM_CONFIG_12KB 0
#define NV1_PRAM_CONFIG_20KB 1
#define NV1_PRAM_CONFIG_36KB 2
#define NV1_PRAM_CONFIG_68KB 3
// Position of RAMPW in RAMIN
#define NV1_RAMPW_POSITION_CONFIG0 0x2c00
#define NV1_RAMPW_POSITION_CONFIG1 0x4c00
#define NV1_RAMPW_POSITION_CONFIG2 0x8c00
#define NV1_RAMPW_POSITION_CONFIG3 0x10c00
// Static RAMPW mirror
#define NV1_PRAMPW 0x606000
#define NV1_RAMPW_SIZE 0x400
//
// PROM
//
#define NV1_PROM 0x601000
#define NV1_PROM_SIZE 32768
// Structures
typedef struct nv1_s
{
nv_base_t nvbase; // Base Nvidia structure
} nv1_t;
// Device Core
void nv1_init();
void nv1_close(void* priv);
void nv1_speed_changed(void *priv);
void nv1_draw_cursor(svga_t* svga, int32_t drawline);
void nv1_recalc_timings(svga_t* svga);
void nv1_force_redraw(void* priv);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,149 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* Riva TNT hardware defines
*
* Authors: Connor Hyde <mario64crashed@gmail.com>
*
* Copyright 2024-2025 Connor Hyde
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <86Box/nv/vid_nv4_defines.h>
//
// Structures
//
// PBUS
// RMA: Access the GPU from real-mode
typedef struct nv4_pbus_rma_s
{
uint32_t addr; // Address to RMA to
uint32_t data; // Data to send to MMIO
uint8_t mode; // the current state of the rma shifting engin
uint8_t rma_regs[NV4_RMA_NUM_REGS]; // The rma registers (saved)
} nv4_pbus_rma_t;
// Bus Configuration
typedef struct nv4_pbus_s
{
uint32_t debug_0;
uint32_t interrupt_status; // Interrupt status
uint32_t interrupt_enable; // Interrupt enable
nv4_pbus_rma_t rma;
} nv4_pbus_t;
// PTIMER
typedef struct nv4_ptimer_s
{
uint32_t interrupt_status; // PTIMER Interrupt status
uint32_t interrupt_enable; // PTIMER Interrupt enable
uint32_t clock_numerator; // PTIMER (tick?) numerator
uint32_t clock_denominator; // PTIMER (tick?) denominator
uint64_t time; // time
uint32_t alarm; // The value of time when there should be an alarm
} nv4_ptimer_t;
// PRAMDAC
typedef struct nv4_pramdac_s
{
uint32_t mclk;
uint32_t vclk;
uint32_t nvclk;
uint32_t clk_coeff_select; // Clock coefficient selection
uint32_t cursor_address;
} nv4_pramdac_t;
// Device Core
typedef struct nv4_s
{
nv_base_t nvbase; // Base Nvidia structure
uint32_t straps; // Straps. See defines
nv4_pbus_t pbus;
nv4_ptimer_t ptimer;
nv4_pramdac_t pramdac;
} nv4_t;
//
// Globals
//
extern const device_config_t nv4_config[];
extern nv4_t* nv4; // Allocated at device startup
#ifdef NV_LOG
// Debug register list
extern nv_register_t nv4_registers[];
#endif
//
// Functions
//
// Device Core
bool nv4_init();
void* nv4_init_stb4400(const device_t* info);
void nv4_close(void* priv);
void nv4_speed_changed(void *priv);
void nv4_draw_cursor(svga_t* svga, int32_t drawline);
void nv4_recalc_timings(svga_t* svga);
void nv4_force_redraw(void* priv);
// I/O
uint8_t nv4_mmio_read8(uint32_t addr, void* priv);
uint16_t nv4_mmio_read16(uint32_t addr, void* priv);
uint32_t nv4_mmio_read32(uint32_t addr, void* priv);
void nv4_mmio_write8(uint32_t addr, uint8_t val, void* priv);
void nv4_mmio_write16(uint32_t addr, uint16_t val, void* priv);
void nv4_mmio_write32(uint32_t addr, uint32_t val, void* priv);
uint8_t nv4_dfb_read8(uint32_t addr, void* priv);
uint16_t nv4_dfb_read16(uint32_t addr, void* priv);
uint32_t nv4_dfb_read32(uint32_t addr, void* priv);
void nv4_dfb_write8(uint32_t addr, uint8_t val, void* priv);
void nv4_dfb_write16(uint32_t addr, uint16_t val, void* priv);
void nv4_dfb_write32(uint32_t addr, uint32_t val, void* priv);
uint8_t nv4_ramin_read8(uint32_t addr, void* priv);
uint16_t nv4_ramin_read16(uint32_t addr, void* priv);
uint32_t nv4_ramin_read32(uint32_t addr, void* priv);
void nv4_ramin_write8(uint32_t addr, uint8_t val, void* priv);
void nv4_ramin_write16(uint32_t addr, uint16_t val, void* priv);
void nv4_ramin_write32(uint32_t addr, uint32_t val, void* priv);
uint8_t nv4_pci_read(int32_t func, int32_t addr, void* priv);
void nv4_pci_write(int32_t func, int32_t addr, uint8_t val, void* priv);
// SVGA
uint8_t nv4_svga_read(uint16_t addr, void* priv);
void nv4_svga_write(uint16_t addr, uint8_t val, void* priv);
// Memory
void nv4_update_mappings();
// PRAMDAC
uint32_t nv4_pramdac_read(uint32_t address);
void nv4_pramdac_write(uint32_t address, uint32_t data);
// We don't implement NVCLK/VCLK because they are too fast
void nv4_pramdac_set_vclk();
void nv4_vclk_tick();
// PTIMER
uint32_t nv4_ptimer_read(uint32_t address);
void nv4_ptimer_write(uint32_t address, uint32_t data);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,23 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* Standard library for implementation of video functionality that is duplicated across multiple cards.
*
*
*
* Authors: Connor Hyde <mario64crashed@gmail.com> <nomorestarfrost@gmail.com>
*
* Copyright 2025 Connor Hyde
*/
/* ROP */
#define VIDEO_ROP_SRC_COPY 0xCC
int32_t video_rop_gdi_ternary(int32_t rop, int32_t src, int32_t dst, int32_t pattern);

View File

@@ -630,6 +630,13 @@ extern const device_t voodoo_3_3500_se_agp_device;
extern const device_t voodoo_3_3500_si_agp_device;
extern const device_t velocity_100_agp_device;
extern const device_t velocity_200_agp_device;
extern const device_t nv1_device_edge2k;
extern const device_t nv1_device_edge3k;
extern const device_t nv3_device_pci;
extern const device_t nv3_device_agp;
extern const device_t nv3t_device_pci;
extern const device_t nv3t_device_agp;
extern const device_t nv4_device_agp;
/* Wyse 700 */
extern const device_t wy700_device;

View File

@@ -171,7 +171,15 @@ add_library(ui STATIC
qt_newfloppydialog.ui
qt_harddiskdialog.cpp
qt_harddiskdialog.hpp
qt_harddiskdialog.ui
qt_harddiskdialog.ui
qt_gpudebug_vram.cpp
qt_gpudebug_vram.hpp
qt_gpudebug_vram.ui
qt_gpudebug_visualnv.cpp
qt_gpudebug_visualnv.hpp
qt_gpudebug_visualnv.ui
qt_harddrive_common.cpp
qt_harddrive_common.hpp

View File

@@ -0,0 +1,177 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* GPU Debugging Tools - Visual NV Debugger implementation
*
*
*
* Authors: starfrost
*
* Copyright 2025 starfrost
*/
/* C++ includes */
#include <cstdbool>
#include <cstdint>
/* Qt includes*/
#include <QDebug>
#include <QComboBox>
#include <QPushButton>
#include <QFormLayout>
#include <QSpinBox>
#include <QCheckBox>
#include <QFrame>
#include <QLineEdit>
#include <QPlainTextEdit>
#include <QLabel>
#include <QDir>
#include <QSettings>
#include <QFileDialog>
#include <qt_gpudebug_visualnv.hpp>
#include "ui_qt_gpudebug_visualnv.h"
/* 86Box core includes */
extern "C"
{
/* NOTE: DO NOT REMOVE */
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/pci.h>
#include <86box/rom.h>
#include <86box/video.h>
#include <86box/nv/vid_nv.h>
#include <86box/nv/vid_nv3.h>
}
VisualNVDialog::VisualNVDialog(QWidget *parent)
: QDialog(parent)
, ui(new Ui::VisualNVDialog)
{
ui->setupUi(this);
connect(ui->btnLoadSavestate, &QPushButton::clicked, this, &VisualNVDialog::on_btnLoadSavestate_clicked);
connect(ui->fbStartAddress, &QPlainTextEdit::textChanged, this, &VisualNVDialog::on_fbStartAddress_changed);
connect(ui->bPitch0Value, &QPlainTextEdit::textChanged, this, &VisualNVDialog::on_bPitch0Value_changed);
connect(ui->bPitch1Value, &QPlainTextEdit::textChanged, this, &VisualNVDialog::on_bPitch1Value_changed);
}
// VisualNV dialog destructor
VisualNVDialog::~VisualNVDialog()
{
}
void VisualNVDialog::on_btnLoadSavestate_clicked()
{
if (!nv3)
return;
QString bar0_file_name = QFileDialog::getOpenFileName
(
this,
tr("Please provide NVPlay 0.3.0.7+ NV3BAR0.BIN file"),
".",
tr("NVPlay MMIO Dump Files (*.bin)")
);
QString bar1_file_name = QFileDialog::getOpenFileName
(
this,
tr("Please provide NVPlay 0.3.0.7+ NV3BAR1.BIN file"),
".",
tr("NVPlay VRAM/RAMIN Dump Files (*.bin)")
);
//
// Open both dump files
//
QFile bar0(bar0_file_name);
QFile bar1(bar1_file_name);
if (!bar0.open(QIODevice::ReadOnly))
{
warning("Failed to open NV3BAR0.bin!");
return;
}
if (!bar1.open(QIODevice::ReadOnly))
{
warning("Failed to open NV3BAR1.bin!");
return;
}
if (bar0.size() != NV3_MMIO_SIZE
|| bar1.size() != NV3_MMIO_SIZE)
{
warning("NV3BAR0.bin and NV3BAR1.bin must be 16MB!");
bar0.close();
bar1.close();
return;
}
// Load VRAM contents only for now. Todo: MMIO+RAMIN
QString oldTitle = this->windowTitle();
this->setWindowTitle(tr("RIVA 128 Realtime Debugger: Savestate Loading..."));
bar1.read((char*)nv3->nvbase.svga.vram, nv3->nvbase.vram_amount);
this->setWindowTitle(oldTitle);
}
void VisualNVDialog::on_fbStartAddress_changed()
{
if (nv3)
{
nv3->nvbase.debug_dba_enabled = true;
bool ok = true;
nv3->nvbase.debug_dba = ui->fbStartAddress->toPlainText().toInt(&ok);
if (!ok)
nv3->nvbase.debug_dba_enabled = false;
}
}
void VisualNVDialog::on_bPitch0Value_changed()
{
if (nv3)
{
bool ok = true;
uint32_t old_bpitch = nv3->pgraph.bpitch[0];
nv3->pgraph.bpitch[0] = ui->bPitch0Value->toPlainText().toInt(&ok);
if (!ok)
nv3->pgraph.bpitch[0] = old_bpitch;
}
}
void VisualNVDialog::on_bPitch1Value_changed()
{
if (nv3)
{
bool ok = true;
uint32_t old_bpitch = nv3->pgraph.bpitch[1];
nv3->pgraph.bpitch[1] = ui->bPitch0Value->toPlainText().toInt(&ok);
if (!ok)
nv3->pgraph.bpitch[1] = old_bpitch;
}
}

View File

@@ -0,0 +1,46 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* GPU Debugging Tools - VRAM Viewer headers
*
*
*
* Authors: starfrost
*
* Copyright 2025 starfrost
*/
#pragma once
#include <QDialog>
namespace Ui
{
class VisualNVDialog;
}
class VisualNVDialog : public QDialog
{
Q_OBJECT
public:
explicit VisualNVDialog(QWidget *parent = nullptr);
~VisualNVDialog();
void on_btnLoadSavestate_clicked();
void on_fbStartAddress_changed();
void on_bPitch0Value_changed();
void on_bPitch1Value_changed();
protected:
private:
Ui::VisualNVDialog* ui;
};

View File

@@ -0,0 +1,213 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>VisualNVDialog</class>
<widget class="QDialog" name="VisualNVDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>620</width>
<height>350</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>620</width>
<height>350</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>620</width>
<height>350</height>
</size>
</property>
<property name="windowTitle">
<string>RIVA 128 Realtime Debugger</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="4" column="0">
<widget class="QWidget" name="widget" native="true">
<widget class="QGroupBox" name="groupBox">
<property name="geometry">
<rect>
<x>10</x>
<y>0</y>
<width>201</width>
<height>171</height>
</rect>
</property>
<property name="title">
<string>PFIFO State</string>
</property>
</widget>
<widget class="QGroupBox" name="groupBox_2">
<property name="geometry">
<rect>
<x>220</x>
<y>0</y>
<width>361</width>
<height>171</height>
</rect>
</property>
<property name="title">
<string>PGRAPH State</string>
</property>
</widget>
<widget class="QGroupBox" name="groupBox_3">
<property name="geometry">
<rect>
<x>10</x>
<y>180</y>
<width>571</width>
<height>141</height>
</rect>
</property>
<property name="title">
<string>VRAM Control</string>
</property>
<widget class="QPlainTextEdit" name="fbStartAddress">
<property name="geometry">
<rect>
<x>160</x>
<y>30</y>
<width>104</width>
<height>21</height>
</rect>
</property>
</widget>
<widget class="QLabel" name="label">
<property name="geometry">
<rect>
<x>10</x>
<y>30</y>
<width>151</width>
<height>21</height>
</rect>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;VRAM Visual Aperture Start&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
<widget class="QLabel" name="label_2">
<property name="geometry">
<rect>
<x>390</x>
<y>30</y>
<width>91</width>
<height>16</height>
</rect>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;FB Start Address:&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
<widget class="QLabel" name="label_3">
<property name="geometry">
<rect>
<x>480</x>
<y>30</y>
<width>81</width>
<height>16</height>
</rect>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:700;&quot;&gt;PLACEHOLDER&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
<widget class="QPlainTextEdit" name="plainTextEdit_2">
<property name="geometry">
<rect>
<x>160</x>
<y>60</y>
<width>104</width>
<height>21</height>
</rect>
</property>
</widget>
<widget class="QLabel" name="label_4">
<property name="geometry">
<rect>
<x>10</x>
<y>60</y>
<width>151</width>
<height>21</height>
</rect>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Pixel Depth&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
<widget class="QPushButton" name="btnLoadSavestate">
<property name="geometry">
<rect>
<x>10</x>
<y>110</y>
<width>231</width>
<height>24</height>
</rect>
</property>
<property name="text">
<string>Load nvplay savestate from real hardware</string>
</property>
</widget>
<widget class="QLabel" name="BPITCH">
<property name="geometry">
<rect>
<x>390</x>
<y>60</y>
<width>61</width>
<height>16</height>
</rect>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;BPITCH[0]:&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
<widget class="QPlainTextEdit" name="bPitch0Value">
<property name="geometry">
<rect>
<x>460</x>
<y>60</y>
<width>104</width>
<height>21</height>
</rect>
</property>
</widget>
<widget class="QPlainTextEdit" name="bPitch1Value">
<property name="geometry">
<rect>
<x>460</x>
<y>90</y>
<width>104</width>
<height>21</height>
</rect>
</property>
</widget>
<widget class="QLabel" name="BPITCH_2">
<property name="geometry">
<rect>
<x>390</x>
<y>90</y>
<width>61</width>
<height>16</height>
</rect>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;BPITCH[1]:&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</widget>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,54 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* GPU Debugging Tools - VRAM Viewer implementation
*
*
*
* Authors: starfrost
*
* Copyright 2025 starfrost
*/
/* C++ includes */
#include <cstdbool>
#include <cstdint>
/* Qt includes*/
#include <QDebug>
#include <QComboBox>
#include <QPushButton>
#include <QFormLayout>
#include <QSpinBox>
#include <QCheckBox>
#include <QFrame>
#include <QLineEdit>
#include <QLabel>
#include <QDir>
#include <QSettings>
#include <qt_gpudebug_vram.hpp>
#include "ui_qt_gpudebug_vram.h"
/* 86Box core includes */
extern "C"
{
}
GPUDebugVRAMDialog::GPUDebugVRAMDialog(QWidget *parent)
: QDialog(parent)
, ui(new Ui::GPUDebugVRAMDialog)
{
ui->setupUi(this);
}
GPUDebugVRAMDialog::~GPUDebugVRAMDialog()
{
}

View File

@@ -0,0 +1,40 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* GPU Debugging Tools - VRAM Viewer headers
*
*
*
* Authors: starfrost
*
* Copyright 2025 starfrost
*/
#pragma once
#include <QDialog>
namespace Ui
{
class GPUDebugVRAMDialog;
}
class GPUDebugVRAMDialog : public QDialog
{
Q_OBJECT
public:
explicit GPUDebugVRAMDialog(QWidget *parent = nullptr);
~GPUDebugVRAMDialog();
protected:
private:
Ui::GPUDebugVRAMDialog* ui;
};

View File

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>GPUDebugVRAMDialog</class>
<widget class="QDialog" name="GPUDebugVRAMDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>600</width>
<height>400</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>600</width>
<height>400</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>600</width>
<height>400</height>
</size>
</property>
<property name="windowTitle">
<string>VRAM Viewer</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="5" column="0">
<layout class="QGridLayout" name="gridLayout_2"/>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -24,6 +24,8 @@
#include "qt_mainwindow.hpp"
#include "ui_qt_mainwindow.h"
#include "ui_qt_gpudebug_vram.h"
#include "ui_qt_gpudebug_visualnv.h"
#include "qt_specifydimensions.h"
#include "qt_soundgain.hpp"
@@ -106,6 +108,8 @@ void qt_set_sequence_auto_mnemonic(bool b);
#include "qt_mediamenu.hpp"
#include "qt_util.hpp"
#include "qt_gpudebug_vram.hpp"
#if defined __unix__ && !defined __HAIKU__
# ifndef Q_OS_MACOS
# include "evdev_keyboard.hpp"
@@ -285,29 +289,7 @@ MainWindow::MainWindow(QWidget *parent)
this->setWindowTitle(QString("%1 - %2 %3").arg(vmname, EMU_NAME, EMU_VERSION_FULL));
connect(this, &MainWindow::hardResetCompleted, this, [this]() {
ui->actionMCA_devices->setVisible(machine_has_bus(machine, MACHINE_BUS_MCA));
num_label->setVisible(machine_has_bus(machine, MACHINE_BUS_PS2_PORTS | MACHINE_BUS_AT_KBD));
scroll_label->setVisible(machine_has_bus(machine, MACHINE_BUS_PS2_PORTS | MACHINE_BUS_AT_KBD));
caps_label->setVisible(machine_has_bus(machine, MACHINE_BUS_PS2_PORTS | MACHINE_BUS_AT_KBD));
int ext_ax_kbd = machine_has_bus(machine, MACHINE_BUS_PS2_PORTS | MACHINE_BUS_AT_KBD) &&
(keyboard_type == KEYBOARD_TYPE_AX);
int int_ax_kbd = machine_has_flags(machine, MACHINE_KEYBOARD_JIS) &&
!machine_has_bus(machine, MACHINE_BUS_PS2_PORTS);
kana_label->setVisible(ext_ax_kbd || int_ax_kbd);
while (QApplication::overrideCursor())
QApplication::restoreOverrideCursor();
#ifdef USE_WACOM
ui->menuTablet_tool->menuAction()->setVisible(mouse_input_mode >= 1);
#else
ui->menuTablet_tool->menuAction()->setVisible(false);
#endif
bool enable_comp_option = false;
for (int i = 0; i < MONITORS_NUM; i++) {
if (monitors[i].mon_composite) { enable_comp_option = true; break; }
}
ui->actionCGA_composite_settings->setEnabled(enable_comp_option);
onHardResetCompleted();
});
connect(this, &MainWindow::showMessageForNonQtThread, this, &MainWindow::showMessage_, Qt::QueuedConnection);
@@ -908,6 +890,50 @@ MainWindow::MainWindow(QWidget *parent)
updateShortcuts();
}
void MainWindow::onHardResetCompleted()
{
ui->actionMCA_devices->setVisible(machine_has_bus(machine, MACHINE_BUS_MCA));
num_label->setVisible(machine_has_bus(machine, MACHINE_BUS_PS2_PORTS | MACHINE_BUS_AT_KBD));
scroll_label->setVisible(machine_has_bus(machine, MACHINE_BUS_PS2_PORTS | MACHINE_BUS_AT_KBD));
caps_label->setVisible(machine_has_bus(machine, MACHINE_BUS_PS2_PORTS | MACHINE_BUS_AT_KBD));
int ext_ax_kbd = machine_has_bus(machine, MACHINE_BUS_PS2_PORTS | MACHINE_BUS_AT_KBD) &&
(keyboard_type == KEYBOARD_TYPE_AX);
int int_ax_kbd = machine_has_flags(machine, MACHINE_KEYBOARD_JIS) &&
!machine_has_bus(machine, MACHINE_BUS_PS2_PORTS);
kana_label->setVisible(ext_ax_kbd || int_ax_kbd);
while (QApplication::overrideCursor())
QApplication::restoreOverrideCursor();
#ifdef USE_WACOM
ui->menuTablet_tool->menuAction()->setVisible(mouse_input_mode >= 1);
#else
ui->menuTablet_tool->menuAction()->setVisible(false);
#endif
bool enable_comp_option = false;
for (int i = 0; i < MONITORS_NUM; i++) {
if (monitors[i].mon_composite) { enable_comp_option = true; break; }
}
ui->actionCGA_composite_settings->setEnabled(enable_comp_option);
#ifdef ENABLE_NV_LOG
/*
THIS CODE SUCKS AND THIS DESIGN IS TERRIBLE - EVERYTHING ABOUT IT IS BAD AND WRONG.
ENTIRE DEVICE SUBSYSTEM IDEALLY WOULD BE DECOUPLED FROM UI BUT MEH
*/
const device_t* vid_device = video_card_getdevice(gfxcard[0]);
bool is_nv3 = (vid_device == &nv3_device_agp
|| vid_device == &nv3_device_pci
|| vid_device == &nv3t_device_agp
|| vid_device == &nv3t_device_pci);
ui->actionDebug_GPUDebug_VisualNv->setVisible(is_nv3);
#endif
}
void
MainWindow::closeEvent(QCloseEvent *event)
{
@@ -2525,3 +2551,29 @@ void MainWindow::on_actionCGA_composite_settings_triggered()
config_save();
}
void MainWindow::on_actionDebug_GPUDebug_VRAM_triggered()
{
debugVramDialog = new GPUDebugVRAMDialog(this);
debugVramDialog->setWindowFlag(Qt::CustomizeWindowHint, true);
debugVramDialog->setWindowFlag(Qt::WindowTitleHint, true);
debugVramDialog->setWindowFlag(Qt::WindowSystemMenuHint, false);
// If I have this as a NON-MODAL dialog, input is just eaten without doing anything
// WTF?!?!?!?!?
//debugVramDialog->show();
debugVramDialog->exec();
}
void MainWindow::on_actionDebug_GPUDebug_VisualNv_triggered()
{
visualNvDialog = new VisualNVDialog(this);
visualNvDialog->setWindowFlag(Qt::CustomizeWindowHint, true);
visualNvDialog->setWindowFlag(Qt::WindowTitleHint, true);
visualNvDialog->setWindowFlag(Qt::WindowSystemMenuHint, false);
// If I have this as a NON-MODAL dialog, input is just eaten without doing anything
// WTF?!?!?!?!?
//visualNvDialog->show();
visualNvDialog->exec();
}

View File

@@ -17,6 +17,11 @@
extern QTimer discordupdate;
// NON-modal dialogs
#include "qt_gpudebug_vram.hpp"
#include "qt_gpudebug_visualnv.hpp"
class MediaMenu;
class RendererStack;
@@ -74,6 +79,8 @@ signals:
public slots:
void showSettings();
void hardReset();
void onHardResetCompleted();
void togglePause();
void initRendererMonitorSlot(int monitor_index);
void destroyRendererMonitorSlot(int monitor_index);
@@ -136,7 +143,9 @@ private slots:
void on_actionPreferences_triggered();
void on_actionEnable_Discord_integration_triggered(bool checked);
void on_actionRenderer_options_triggered();
void on_actionDebug_GPUDebug_VRAM_triggered();
void on_actionDebug_GPUDebug_VisualNv_triggered();
void refreshMediaMenu();
void showMessage_(int flags, const QString &header, const QString &message, bool richText, std::atomic_bool* done = nullptr);
void getTitle_(wchar_t *title);
@@ -175,6 +184,10 @@ private slots:
private:
Ui::MainWindow *ui;
// NON-modal dialogs - these use ::show() and therefore have to be maintained as objects
GPUDebugVRAMDialog *debugVramDialog;
VisualNVDialog *visualNvDialog;
std::unique_ptr<MachineStatus> status;
std::shared_ptr<MediaMenu> mm;

View File

@@ -251,11 +251,19 @@
<addaction name="actionAbout_86Box"/>
<addaction name="actionAbout_Qt"/>
</widget>
<widget class="QMenu" name="menuDebug">
<property name="title">
<string>&amp;Debugging Tools</string>
</property>
<addaction name="actionDebug_GPUDebug_VRAM"/>
<addaction name="actionDebug_GPUDebug_VisualNv"/>
</widget>
<addaction name="menuAction"/>
<addaction name="menuView"/>
<addaction name="menuMedia"/>
<addaction name="menuTools"/>
<addaction name="menuAbout"/>
<addaction name="menuDebug"/>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<widget class="QToolBar" name="toolBar">
@@ -919,7 +927,27 @@
<action name="actionCGA_composite_settings">
<property name="text">
<string>&amp;CGA composite settings...</string>
</property>
</property>
</action>
<action name="actionDebug_GPUDebug_VRAM">
<property name="text">
<string>GPU Debug - VRAM Viewer</string>
</property>
</action>
<action name="actionDebug_GPUDebug_VisualNv">
<property name="text">
<string>RIVA 128 Realtime Debugger</string>
</property>
</action>
<action name="actionDebug_GPUDebug_VRAM">
<property name="text">
<string>GPU Debug - VRAM Viewer</string>
</property>
</action>
<action name="actionDebug_GPUDebug_VisualNv">
<property name="text">
<string>RIVA 128 Realtime Debugger</string>
</property>
</action>
<action name="action_Full_screen_stretch_gl">
<property name="checkable">
@@ -1049,6 +1077,54 @@
<string>&amp;8x</string>
</property>
</action>
<action name="action_Full_screen_stretch_gl">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>&amp;Full screen stretch</string>
</property>
</action>
<action name="action_4_3_gl">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>&amp;4:3</string>
</property>
</action>
<action name="action_Square_pixels_keep_ratio_gl">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>&amp;Square pixels (Keep ratio)</string>
</property>
</action>
<action name="action_Integer_scale_gl">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>&amp;Integer scale</string>
</property>
</action>
<action name="action4_3_Integer_scale_gl">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>4:&amp;3 Integer scale</string>
</property>
</action>
<action name="action_1x">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>&amp;1x</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>

View File

@@ -1525,7 +1525,7 @@ emu8k_outw(uint16_t addr, uint16_t val, void *priv)
default:
break;
}
emu8k_log("EMU8K WRITE: Unknown register write: %04X-%02X(%d/%d): %04X \n", addr, (emu8k->cur_reg) << 5 | emu8k->cur_voice,
emu8k_log("EMU8K WRITE: : Unknown register write: %04X-%02X(%d/%d): %04X \n", addr, (emu8k->cur_reg) << 5 | emu8k->cur_voice,
emu8k->cur_reg, emu8k->cur_voice, val);
}

View File

@@ -798,7 +798,7 @@ sb_ct1335_mixer_write(uint16_t addr, uint8_t val, void *priv)
break;
default:
sb_log("sb_ct1335: Unknown register WRITE: %02X\t%02X\n", mixer->index, mixer->regs[mixer->index]);
sb_log("sb_ct1335: : Unknown register write: %02X\t%02X\n", mixer->index, mixer->regs[mixer->index]);
break;
}
}
@@ -899,7 +899,7 @@ sb_ct1345_mixer_write(uint16_t addr, uint8_t val, void *priv)
break;
default:
sb_log("sb_ct1345: Unknown register WRITE: %02X\t%02X\n", mixer->index, mixer->regs[mixer->index]);
sb_log("sb_ct1345: : Unknown register write: %02X\t%02X\n", mixer->index, mixer->regs[mixer->index]);
break;
}
}

View File

@@ -24,4 +24,7 @@ add_library(utils OBJECT
ini.c
log.c
random.c
# VIDEO
video/video_rop.c
)

View File

@@ -44,6 +44,7 @@ typedef struct log_t {
char **cyclic_buff;
int32_t cyclic_last_line;
int32_t log_cycles;
int32_t last_repeat_order;
} log_t;
/* File to log output to. */
@@ -219,9 +220,18 @@ log_out_cyclic(void* priv, const char* fmt, va_list ap)
}
if (is_cycle) {
if (log->cyclic_last_line % repeat_order == 0) {
log->log_cycles++;
if (is_cycle)
{
if (log->cyclic_last_line % repeat_order == 0)
{
log->log_cycles++;
// If the order of the log repeat changes
if (log->last_repeat_order != repeat_order
&& log->last_repeat_order > 0)
{
log->log_cycles = 1;
}
if (log->log_cycles == 1) {
/*
@@ -265,10 +275,13 @@ log_out_cyclic(void* priv, const char* fmt, va_list ap)
}
log->cyclic_last_line++;
log->last_repeat_order = repeat_order;
}
}
#endif
void
log_fatal(void *priv, const char *fmt, ...)
{

790
src/utils/video/video_rop.c Normal file
View File

@@ -0,0 +1,790 @@
#include <stdint.h>
#include <86box/utils/video_stdlib.h>
/*
Implements a standard GDI ternary rop for e.g. bitblit acceleration.
For further information on this function, refer to the documentation on Win32 GDI:
https://learn.microsoft.com/en-us/windows/win32/gdi/binary-raster-operations
This is currently used in the following graphics cards: Tseng Labs ET4000/32p, Cirrus Logic CL-GD54xx, 3dfx Voodoo Banshee/Voodoo 3, Trident TGUI9440,
S3 ViRGE, C&T 69000, ATI Mach64, and NVidia RIVA 128
*/
int32_t video_rop_gdi_ternary(int32_t rop, int32_t src, int32_t dst, int32_t pattern)
{
uint32_t out = 0x00;
switch (rop)
{
case 0x00:
out = 0;
break;
case 0x01:
out = ~(dst | (pattern | src));
break;
case 0x02:
out = dst & ~(pattern | src);
break;
case 0x03:
out = ~(pattern | src);
break;
case 0x04:
out = src & ~(dst | pattern);
break;
case 0x05:
out = ~(dst | pattern);
break;
case 0x06:
out = ~(pattern | ~(dst ^ src));
break;
case 0x07:
out = ~(pattern | (dst & src));
break;
case 0x08:
out = src & (dst & ~pattern);
break;
case 0x09:
out = ~(pattern | (dst ^ src));
break;
case 0x0a:
out = dst & ~pattern;
break;
case 0x0b:
out = ~(pattern | (src & ~dst));
break;
case 0x0c:
out = src & ~pattern;
break;
case 0x0d:
out = ~(pattern | (dst & ~src));
break;
case 0x0e:
out = ~(pattern | ~(dst | src));
break;
case 0x0f:
out = ~pattern;
break;
case 0x10:
out = pattern & ~(dst | src);
break;
case 0x11:
out = ~(dst | src);
break;
case 0x12:
out = ~(src | ~(dst ^ pattern));
break;
case 0x13:
out = ~(src | (dst & pattern));
break;
case 0x14:
out = ~(dst | ~(pattern ^ src));
break;
case 0x15:
out = ~(dst | (pattern & src));
break;
case 0x16:
out = pattern ^ (src ^ (dst & ~(pattern & src)));
break;
case 0x17:
out = ~(src ^ ((src ^ pattern) & (dst ^ src)));
break;
case 0x18:
out = (src ^ pattern) & (pattern ^ dst);
break;
case 0x19:
out = ~(src ^ (dst & ~(pattern & src)));
break;
case 0x1a:
out = pattern ^ (dst | (src & pattern));
break;
case 0x1b:
out = ~(src ^ (dst & (pattern ^ src)));
break;
case 0x1c:
out = pattern ^ (src | (dst & pattern));
break;
case 0x1d:
out = ~(dst ^ (src & (pattern ^ dst)));
break;
case 0x1e:
out = pattern ^ (dst | src);
break;
case 0x1f:
out = ~(pattern & (dst | src));
break;
case 0x20:
out = dst & (pattern & ~src);
break;
case 0x21:
out = ~(src | (dst ^ pattern));
break;
case 0x22:
out = dst & ~src;
break;
case 0x23:
out = ~(src | (pattern & ~dst));
break;
case 0x24:
out = (src ^ pattern) & (dst ^ src);
break;
case 0x25:
out = ~(pattern ^ (dst & ~(src & pattern)));
break;
case 0x26:
out = src ^ (dst | (pattern & src));
break;
case 0x27:
out = src ^ (dst | ~(pattern ^ src));
break;
case 0x28:
out = dst & (pattern ^ src);
break;
case 0x29:
out = ~(pattern ^ (src ^ (dst | (pattern & src))));
break;
case 0x2a:
out = dst & ~(pattern & src);
break;
case 0x2b:
out = ~(src ^ ((src ^ pattern) & (pattern ^ dst)));
break;
case 0x2c:
out = src ^ (pattern & (dst | src));
break;
case 0x2d:
out = pattern ^ (src | ~dst);
break;
case 0x2e:
out = pattern ^ (src | (dst ^ pattern));
break;
case 0x2f:
out = ~(pattern & (src | ~dst));
break;
case 0x30:
out = pattern & ~src;
break;
case 0x31:
out = ~(src | (dst & ~pattern));
break;
case 0x32:
out = src ^ (dst | (pattern | src));
break;
case 0x33:
out = ~src;
break;
case 0x34:
out = src ^ (pattern | (dst & src));
break;
case 0x35:
out = src ^ (pattern | ~(dst ^ src));
break;
case 0x36:
out = src ^ (dst | pattern);
break;
case 0x37:
out = ~(src & (dst | pattern));
break;
case 0x38:
out = pattern ^ (src & (dst | pattern));
break;
case 0x39:
out = src ^ (pattern | ~dst);
break;
case 0x3a:
out = src ^ (pattern | (dst ^ src));
break;
case 0x3b:
out = ~(src & (pattern | ~dst));
break;
case 0x3c:
out = pattern ^ src;
break;
case 0x3d:
out = src ^ (pattern | ~(dst | src));
break;
case 0x3e:
out = src ^ (pattern | (dst & ~src));
break;
case 0x3f:
out = ~(pattern & src);
break;
case 0x40:
out = pattern & (src & ~dst);
break;
case 0x41:
out = ~(dst | (pattern ^ src));
break;
case 0x42:
out = (src ^ dst) & (pattern ^ dst);
break;
case 0x43:
out = ~(src ^ (pattern & ~(dst & src)));
break;
case 0x44:
out = src & ~dst;
break;
case 0x45:
out = ~(dst | (pattern & ~src));
break;
case 0x46:
out = dst ^ (src | (pattern & dst));
break;
case 0x47:
out = ~(pattern ^ (src & (dst ^ pattern)));
break;
case 0x48:
out = src & (dst ^ pattern);
break;
case 0x49:
out = ~(pattern ^ (dst ^ (src | (pattern & dst))));
break;
case 0x4a:
out = dst ^ (pattern & (src | dst));
break;
case 0x4b:
out = pattern ^ (dst | ~src);
break;
case 0x4c:
out = src & ~(dst & pattern);
break;
case 0x4d:
out = ~(src ^ ((src ^ pattern) | (dst ^ src)));
break;
case 0x4e:
out = pattern ^ (dst | (src ^ pattern));
break;
case 0x4f:
out = ~(pattern & (dst | ~src));
break;
case 0x50:
out = pattern & ~dst;
break;
case 0x51:
out = ~(dst | (src & ~pattern));
break;
case 0x52:
out = dst ^ (pattern | (src & dst));
break;
case 0x53:
out = ~(src ^ (pattern & (dst ^ src)));
break;
case 0x54:
out = ~(dst | ~(pattern | src));
break;
case 0x55:
out = ~dst;
break;
case 0x56:
out = dst ^ (pattern | src);
break;
case 0x57:
out = ~(dst & (pattern | src));
break;
case 0x58:
out = pattern ^ (dst & (src | pattern));
break;
case 0x59:
out = dst ^ (pattern | ~src);
break;
case 0x5a:
out = dst ^ pattern;
break;
case 0x5b:
out = dst ^ (pattern | ~(src | dst));
break;
case 0x5c:
out = dst ^ (pattern | (src ^ dst));
break;
case 0x5d:
out = ~(dst & (pattern | ~src));
break;
case 0x5e:
out = dst ^ (pattern | (src & ~dst));
break;
case 0x5f:
out = ~(dst & pattern);
break;
case 0x60:
out = pattern & (dst ^ src);
break;
case 0x61:
out = ~(dst ^ (src ^ (pattern | (dst & src))));
break;
case 0x62:
out = dst ^ (src & (pattern | dst));
break;
case 0x63:
out = src ^ (dst | ~pattern);
break;
case 0x64:
out = src ^ (dst & (pattern | src));
break;
case 0x65:
out = dst ^ (src | ~pattern);
break;
case 0x66:
out = dst ^ src;
break;
case 0x67:
out = src ^ (dst | ~(pattern | src));
break;
case 0x68:
out = ~(dst ^ (src ^ (pattern | ~(dst | src))));
break;
case 0x69:
out = ~(pattern ^ (dst ^ src));
break;
case 0x6a:
out = dst ^ (pattern & src);
break;
case 0x6b:
out = ~(pattern ^ (src ^ (dst & (pattern | src))));
break;
case 0x6c:
out = src ^ (dst & pattern);
break;
case 0x6d:
out = ~(pattern ^ (dst ^ (src & (pattern | dst))));
break;
case 0x6e:
out = src ^ (dst & (pattern | ~src));
break;
case 0x6f:
out = ~(pattern & ~(dst ^ src));
break;
case 0x70:
out = pattern & ~(dst & src);
break;
case 0x71:
out = ~(src ^ ((src ^ dst) & (pattern ^ dst)));
break;
case 0x72:
out = src ^ (dst | (pattern ^ src));
break;
case 0x73:
out = ~(src & (dst | ~pattern));
break;
case 0x74:
out = dst ^ (src | (pattern ^ dst));
break;
case 0x75:
out = ~(dst & (src | ~pattern));
break;
case 0x76:
out = src ^ (dst | (pattern & ~src));
break;
case 0x77:
out = ~(dst & src);
break;
case 0x78:
out = pattern ^ (dst & src);
break;
case 0x79:
out = ~(dst ^ (src ^ (pattern & (dst | src))));
break;
case 0x7a:
out = dst ^ (pattern & (src | ~dst));
break;
case 0x7b:
out = ~(src & ~(dst ^ pattern));
break;
case 0x7c:
out = src ^ (pattern & (dst | ~src));
break;
case 0x7d:
out = ~(dst & ~(pattern ^ src));
break;
case 0x7e:
out = (src ^ pattern) | (dst ^ src);
break;
case 0x7f:
out = ~(dst & (pattern & src));
break;
case 0x80:
out = dst & (pattern & src);
break;
case 0x81:
out = ~((src ^ pattern) | (dst ^ src));
break;
case 0x82:
out = dst & ~(pattern ^ src);
break;
case 0x83:
out = ~(src ^ (pattern & (dst | ~src)));
break;
case 0x84:
out = src & ~(dst ^ pattern);
break;
case 0x85:
out = ~(pattern ^ (dst & (src | ~pattern)));
break;
case 0x86:
out = dst ^ (src ^ (pattern & (dst | src)));
break;
case 0x87:
out = ~(pattern ^ (dst & src));
break;
case 0x88:
out = dst & src;
break;
case 0x89:
out = ~(src ^ (dst | (pattern & ~src)));
break;
case 0x8a:
out = dst & (src | ~pattern);
break;
case 0x8b:
out = ~(dst ^ (src | (pattern ^ dst)));
break;
case 0x8c:
out = src & (dst | ~pattern);
break;
case 0x8d:
out = ~(src ^ (dst | (pattern ^ src)));
break;
case 0x8e:
out = src ^ ((src ^ dst) & (pattern ^ dst));
break;
case 0x8f:
out = ~(pattern & ~(dst & src));
break;
case 0x90:
out = pattern & ~(dst ^ src);
break;
case 0x91:
out = ~(src ^ (dst & (pattern | ~src)));
break;
case 0x92:
out = dst ^ (pattern ^ (src & (dst | pattern)));
break;
case 0x93:
out = ~(src ^ (pattern & dst));
break;
case 0x94:
out = pattern ^ (src ^ (dst & (pattern | src)));
break;
case 0x95:
out = ~(dst ^ (pattern & src));
break;
case 0x96:
out = dst ^ (pattern ^ src);
break;
case 0x97:
out = pattern ^ (src ^ (dst | ~(pattern | src)));
break;
case 0x98:
out = ~(src ^ (dst | ~(pattern | src)));
break;
case 0x99:
out = ~(dst ^ src);
break;
case 0x9a:
out = dst ^ (pattern & ~src);
break;
case 0x9b:
out = ~(src ^ (dst & (pattern | src)));
break;
case 0x9c:
out = src ^ (pattern & ~dst);
break;
case 0x9d:
out = ~(dst ^ (src & (pattern | dst)));
break;
case 0x9e:
out = dst ^ (src ^ (pattern | (dst & src)));
break;
case 0x9f:
out = ~(pattern & (dst ^ src));
break;
case 0xa0:
out = dst & pattern;
break;
case 0xa1:
out = ~(pattern ^ (dst | (src & ~pattern)));
break;
case 0xa2:
out = dst & (pattern | ~src);
break;
case 0xa3:
out = ~(dst ^ (pattern | (src ^ dst)));
break;
case 0xa4:
out = ~(pattern ^ (dst | ~(src | pattern)));
break;
case 0xa5:
out = ~(pattern ^ dst);
break;
case 0xa6:
out = dst ^ (src & ~pattern);
break;
case 0xa7:
out = ~(pattern ^ (dst & (src | pattern)));
break;
case 0xa8:
out = dst & (pattern | src);
break;
case 0xa9:
out = ~(dst ^ (pattern | src));
break;
case 0xaa:
out = dst;
break;
case 0xab:
out = dst | ~(pattern | src);
break;
case 0xac:
out = src ^ (pattern & (dst ^ src));
break;
case 0xad:
out = ~(dst ^ (pattern | (src & dst)));
break;
case 0xae:
out = dst | (src & ~pattern);
break;
case 0xaf:
out = dst | ~pattern;
break;
case 0xb0:
out = pattern & (dst | ~src);
break;
case 0xb1:
out = ~(pattern ^ (dst | (src ^ pattern)));
break;
case 0xb2:
out = src ^ ((src ^ pattern) | (dst ^ src));
break;
case 0xb3:
out = ~(src & ~(dst & pattern));
break;
case 0xb4:
out = pattern ^ (src & ~dst);
break;
case 0xb5:
out = ~(dst ^ (pattern & (src | dst)));
break;
case 0xb6:
out = dst ^ (pattern ^ (src | (dst & pattern)));
break;
case 0xb7:
out = ~(src & (dst ^ pattern));
break;
case 0xb8:
out = pattern ^ (src & (dst ^ pattern));
break;
case 0xb9:
out = ~(dst ^ (src | (pattern & dst)));
break;
case 0xba:
out = dst | (pattern & ~src);
break;
case 0xbb:
out = dst | ~src;
break;
case 0xbc:
out = src ^ (pattern & ~(dst & src));
break;
case 0xbd:
out = ~((src ^ dst) & (pattern ^ dst));
break;
case 0xbe:
out = dst | (pattern ^ src);
break;
case 0xbf:
out = dst | ~(pattern & src);
break;
case 0xc0:
out = pattern & src;
break;
case 0xc1:
out = ~(src ^ (pattern | (dst & ~src)));
break;
case 0xc2:
out = ~(src ^ (pattern | ~(dst | src)));
break;
case 0xc3:
out = ~(pattern ^ src);
break;
case 0xc4:
out = src & (pattern | ~dst);
break;
case 0xc5:
out = ~(src ^ (pattern | (dst ^ src)));
break;
case 0xc6:
out = src ^ (dst & ~pattern);
break;
case 0xc7:
out = ~(pattern ^ (src & (dst | pattern)));
break;
case 0xc8:
out = src & (dst | pattern);
break;
case 0xc9:
out = ~(src ^ (pattern | dst));
break;
case 0xca:
out = dst ^ (pattern & (src ^ dst));
break;
case 0xcb:
out = ~(src ^ (pattern | (dst & src)));
break;
case 0xcc:
out = src;
break;
case 0xcd:
out = src | ~(dst | pattern);
break;
case 0xce:
out = src | (dst & ~pattern);
break;
case 0xcf:
out = src | ~pattern;
break;
case 0xd0:
out = pattern & (src | ~dst);
break;
case 0xd1:
out = ~(pattern ^ (src | (dst ^ pattern)));
break;
case 0xd2:
out = pattern ^ (dst & ~src);
break;
case 0xd3:
out = ~(src ^ (pattern & (dst | src)));
break;
case 0xd4:
out = src ^ ((src ^ pattern) & (pattern ^ dst));
break;
case 0xd5:
out = ~(dst & ~(pattern & src));
break;
case 0xd6:
out = pattern ^ (src ^ (dst | (pattern & src)));
break;
case 0xd7:
out = ~(dst & (pattern ^ src));
break;
case 0xd8:
out = pattern ^ (dst & (src ^ pattern));
break;
case 0xd9:
out = ~(src ^ (dst | (pattern & src)));
break;
case 0xda:
out = dst ^ (pattern & ~(src & dst));
break;
case 0xdb:
out = ~((src ^ pattern) & (dst ^ src));
break;
case 0xdc:
out = src | (pattern & ~dst);
break;
case 0xdd:
out = src | ~dst;
break;
case 0xde:
out = src | (dst ^ pattern);
break;
case 0xdf:
out = src | ~(dst & pattern);
break;
case 0xe0:
out = pattern & (dst | src);
break;
case 0xe1:
out = ~(pattern ^ (dst | src));
break;
case 0xe2:
out = dst ^ (src & (pattern ^ dst));
break;
case 0xe3:
out = ~(pattern ^ (src | (dst & pattern)));
break;
case 0xe4:
out = src ^ (dst & (pattern ^ src));
break;
case 0xe5:
out = ~(pattern ^ (dst | (src & pattern)));
break;
case 0xe6:
out = src ^ (dst & ~(pattern & src));
break;
case 0xe7:
out = ~((src ^ pattern) & (pattern ^ dst));
break;
case 0xe8:
out = src ^ ((src ^ pattern) & (dst ^ src));
break;
case 0xe9:
out = ~(dst ^ (src ^ (pattern & ~(dst & src))));
break;
case 0xea:
out = dst | (pattern & src);
break;
case 0xeb:
out = dst | ~(pattern ^ src);
break;
case 0xec:
out = src | (dst & pattern);
break;
case 0xed:
out = src | ~(dst ^ pattern);
break;
case 0xee:
out = dst | src;
break;
case 0xef:
out = src | (dst | ~pattern);
break;
case 0xf0:
out = pattern;
break;
case 0xf1:
out = pattern | ~(dst | src);
break;
case 0xf2:
out = pattern | (dst & ~src);
break;
case 0xf3:
out = pattern | ~src;
break;
case 0xf4:
out = pattern | (src & ~dst);
break;
case 0xf5:
out = pattern | ~dst;
break;
case 0xf6:
out = pattern | (dst ^ src);
break;
case 0xf7:
out = pattern | ~(dst & src);
break;
case 0xf8:
out = pattern | (dst & src);
break;
case 0xf9:
out = pattern | ~(dst ^ src);
break;
case 0xfa:
out = dst | pattern;
break;
case 0xfb:
out = dst | (pattern | ~src);
break;
case 0xfc:
out = pattern | src;
break;
case 0xfd:
out = pattern | (src | ~dst);
break;
case 0xfe:
out = dst | (pattern | src);
break;
case 0xff:
out = ~0;
break;
}
return out;
}

View File

@@ -11,7 +11,7 @@
# Authors: David Hrdlička, <hrdlickadavid@outlook.com>
#
# Copyright 2020-2021 David Hrdlička.
# Copyright 2025 starfrost
# Copyright 2025 Connor Hyde / starfrost
#
add_library(vid OBJECT
@@ -136,12 +136,77 @@ add_library(vid OBJECT
# Matrox
vid_mga.c
# NVidia (pending)
# NVidia - Core
nv/nv_base.c
nv/nv_rivatimer.c
# NVidia NV1
nv/nv1/nv1_core.c
nv/nv1/nv1_core_config.c
# NVidia RIVA 128 - Subsystems
nv/nv3/nv3_core.c
nv/nv3/nv3_core_config.c
nv/nv3/nv3_core_arbiter.c
nv/nv3/subsystems/nv3_pramdac.c
nv/nv3/subsystems/nv3_pfifo.c
nv/nv3/subsystems/nv3_pgraph.c
nv/nv3/subsystems/nv3_pmc.c
nv/nv3/subsystems/nv3_pme.c
nv/nv3/subsystems/nv3_pextdev.c
nv/nv3/subsystems/nv3_pfb.c
nv/nv3/subsystems/nv3_pbus.c
nv/nv3/subsystems/nv3_pbus_dma.c
nv/nv3/subsystems/nv3_ptimer.c
nv/nv3/subsystems/nv3_pramin.c
nv/nv3/subsystems/nv3_pramin_ramht.c
nv/nv3/subsystems/nv3_pramin_ramfc.c
nv/nv3/subsystems/nv3_pramin_ramro.c
nv/nv3/subsystems/nv3_pvideo.c
nv/nv3/subsystems/nv3_user.c
# NVidia RIVA 128 - Object Classes
nv/nv3/classes/nv3_class_names.c
nv/nv3/classes/nv3_class_shared_methods.c
nv/nv3/classes/nv3_class_001_beta_factor.c
nv/nv3/classes/nv3_class_002_rop.c
nv/nv3/classes/nv3_class_003_chroma_key.c
nv/nv3/classes/nv3_class_004_plane_mask.c
nv/nv3/classes/nv3_class_005_clipping_rectangle.c
nv/nv3/classes/nv3_class_006_pattern.c
nv/nv3/classes/nv3_class_007_rectangle.c
nv/nv3/classes/nv3_class_008_point.c
nv/nv3/classes/nv3_class_009_line.c
nv/nv3/classes/nv3_class_00a_lin.c
nv/nv3/classes/nv3_class_00b_triangle.c
nv/nv3/classes/nv3_class_00c_win95_gdi_text.c
nv/nv3/classes/nv3_class_00d_m2mf.c
nv/nv3/classes/nv3_class_00e_scaled_image_from_mem.c
nv/nv3/classes/nv3_class_010_blit.c
nv/nv3/classes/nv3_class_011_image.c
nv/nv3/classes/nv3_class_012_bitmap.c
nv/nv3/classes/nv3_class_014_transfer2memory.c
nv/nv3/classes/nv3_class_015_stretched_image_from_cpu.c
nv/nv3/classes/nv3_class_017_d3d5_tri_zeta_buffer.c
nv/nv3/classes/nv3_class_018_point_zeta_buffer.c
nv/nv3/classes/nv3_class_01c_image_in_memory.c
# NVidia RIVA 128 - Render
nv/nv3/render/nv3_render_core.c
nv/nv3/render/nv3_render_primitives.c
nv/nv3/render/nv3_render_blit.c
# NVidia RIVA TNT/TNT2 - Core
nv/nv4/nv4_core.c
nv/nv4/nv4_core_io.c
nv/nv4/nv4_core_config.c
nv/nv4/nv4_debug_register_list.c
nv/nv4/subsystems/nv4_pramdac.c
nv/nv4/subsystems/nv4_ptimer.c
# Generic
vid_bochs_vbe.c
)
if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")

110
src/video/nv/nv1/nv1_core.c Normal file
View File

@@ -0,0 +1,110 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* NV3 bringup and device emulation.
*
*
* Authors: Connor Hyde, <mario64crashed@gmail.com> I need a better email address ;^)
*
* Copyright 2024-2025 starfrost
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/io.h>
#include <86box/pci.h>
#include <86box/rom.h> // DEPENDENT!!!
#include <86box/video.h>
#include <86box/nv/vid_nv.h>
#include <86box/nv/vid_nv1.h>
void nv1_init()
{
}
void* nv1_init_edge2k(const device_t *info)
{
}
void* nv1_init_edge3k(const device_t *info)
{
}
void nv1_close(void* priv)
{
}
void nv1_speed_changed(void *priv)
{
}
void nv1_draw_cursor(svga_t* svga, int32_t drawline)
{
}
void nv1_recalc_timings(svga_t* svga)
{
}
void nv1_force_redraw(void* priv)
{
}
// See if the bios rom is available.
int32_t nv1_available(void)
{
return (rom_present(NV1_VBIOS_E3D_2X00)
|| rom_present(NV1_VBIOS_E3D_3X00));
}
// NV3 (RIVA 128)
// PCI
// 2MB or 4MB VRAM
const device_t nv1_device_edge2k =
{
.name = "nVIDIA NV1 [Diamond Edge 3D 2x00] [Not Direct3D Compatible]",
.internal_name = "nv1_edge2k",
.flags = DEVICE_PCI,
.local = 0,
.init = nv1_init_edge2k,
.close = nv1_close,
.speed_changed = nv1_speed_changed,
.force_redraw = nv1_force_redraw,
.available = nv1_available,
.config = nv1_config,
};
// NV3 (RIVA 128)
// AGP
// 2MB or 4MB VRAM
const device_t nv1_device_edge3k =
{
.name = "nVIDIA NV1 [Diamond Edge 3D 3x00] [Not Direct3D Compatible]",
.internal_name = "nv1_edge3k",
.flags = DEVICE_PCI,
.local = 0,
.init = nv1_init_edge3k,
.close = nv1_close,
.speed_changed = nv1_speed_changed,
.force_redraw = nv1_force_redraw,
.available = nv1_available,
.config = nv1_config,
};

View File

@@ -0,0 +1,133 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* Provides NV4 configuration
*
*
* Authors: Connor Hyde, <mario64crashed@gmail.com> I need a better email address ;^)
*
* Copyright 2024-2025 starfrost
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/io.h>
#include <86box/pci.h>
#include <86box/rom.h> // DEPENDENT!!!
#include <86box/video.h>
#include <86box/nv/vid_nv.h>
#include <86box/nv/vid_nv1.h>
const device_config_t nv1_config[] =
{
// Memory configuration
{
.name = "vram_size",
.description = "VRAM Size",
.type = CONFIG_SELECTION,
.default_int = NV1_VRAM_SIZE_4MB,
.selection =
{
// I thought this was never released, but it seems that at least one was released:
// The card was called the "NEC G7AGK"
{
.description = "1 MB",
.value = NV1_VRAM_SIZE_1MB,
},
{
.description = "2 MB",
.value = NV1_VRAM_SIZE_2MB,
},
{
.description = "4 MB",
.value = NV1_VRAM_SIZE_4MB,
},
}
},
// Multithreading configuration
{
.name = "pgraph_threads",
#ifndef RELEASE_BUILD
.description = "PFIFO/PGRAPH - Number of threads to split large object method execution into",
#else
.description = "Render threads",
#endif
.type = CONFIG_SELECTION,
.default_int = 1, // todo: change later
.selection =
{
{
.description = "1 thread (Only use if issues appear with more threads)",
.value = 1,
},
{
.description = "2 threads",
.value = 2,
},
{
.description = "4 threads",
.value = 4,
},
{
.description = "8 threads",
.value = 8,
},
},
},
{
.name = "RAMDAC Type",
.description = "SGS-Thomson RAMDAC type",
.default_int = 0x1764,
.type = CONFIG_SELECTION,
.selection =
{
{
.description = "SGS-Thomson STG-1732X",
.value = 0x1732,
},
{
.description = "SGS-Thomson STG-1764X/NVDAC64",
.value = 0x1764,
},
}
},
{
.name = "Chip type",
.description = "Chip type",
.default_int = 0x1,
.type = CONFIG_SELECTION,
.selection =
{
{
.description = "SGS-Thomson STG-2000",
.value = 0x2000,
},
{
.description = "Nvidia NV1",
.value = 0x1,
},
}
},
#ifndef RELEASE_BUILD
{
.name = "nv_debug_fulllog",
.description = "Disable Cyclical Lines Detection for nv_log (Use for getting full context at cost of VERY large log files)",
.type = CONFIG_BINARY,
.default_int = 0,
},
#endif
{
.type = CONFIG_END
}
};

View File

@@ -0,0 +1,50 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* NV3: Methods for class 0x01 (Beta factor)
*
*
*
* Authors: Connor Hyde, <mario64crashed@gmail.com> I need a better email address ;^)
*
* Copyright 2024-2025 Connor Hyde
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/pci.h>
#include <86box/rom.h>
#include <86box/video.h>
#include <86box/nv/vid_nv.h>
#include <86box/nv/vid_nv3.h>
void nv3_class_001_method(uint32_t param, uint32_t method_id, nv3_ramin_context_t context, nv3_grobj_t grobj)
{
switch (method_id)
{
/* even if we don't do anything with this yet... */
case NV3_BETA_FACTOR:
if (param & 0x80000000) /* bit0 */
nv3->pgraph.beta_factor = 0;
else
nv3->pgraph.beta_factor = param & 0x7F800000;
nv_log("Method Execution: Beta Factor = %02x", nv3->pgraph.beta_factor);
break;
default:
warning("%s: Invalid or unimplemented method 0x%04x\n", nv3_class_names[context.class_id & 0x1F], method_id);
nv3_pgraph_interrupt_invalid(NV3_PGRAPH_INTR_1_SOFTWARE_METHOD_PENDING);
break;
}
}

View File

@@ -0,0 +1,44 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* NV3: Methods for class 0x02 (Render operation)
*
*
*
* Authors: Connor Hyde, <mario64crashed@gmail.com> I need a better email address ;^)
*
* Copyright 2024-2025 Connor Hyde
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/pci.h>
#include <86box/rom.h>
#include <86box/video.h>
#include <86box/nv/vid_nv.h>
#include <86box/nv/vid_nv3.h>
void nv3_class_002_method(uint32_t param, uint32_t method_id, nv3_ramin_context_t context, nv3_grobj_t grobj)
{
switch (method_id)
{
case NV3_ROP_SET_ROP:
nv3->pgraph.rop = param & 0xFF;
nv_log("Method Execution: ROP = %02x\n", nv3->pgraph.rop);
break;
default:
warning("%s: Invalid or unimplemented method 0x%04x\n", nv3_class_names[context.class_id & 0x1F], method_id);
nv3_pgraph_interrupt_invalid(NV3_PGRAPH_INTR_1_SOFTWARE_METHOD_PENDING);
break;
}
}

View File

@@ -0,0 +1,54 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* NV3: Methods for class 0x02 (Chroma/color key)
*
*
*
* Authors: Connor Hyde, <mario64crashed@gmail.com> I need a better email address ;^)
*
* Copyright 2024-2025 Connor Hyde
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/pci.h>
#include <86box/rom.h>
#include <86box/video.h>
#include <86box/nv/vid_nv.h>
#include <86box/nv/vid_nv3.h>
void nv3_class_003_method(uint32_t param, uint32_t method_id, nv3_ramin_context_t context, nv3_grobj_t grobj)
{
switch (method_id)
{
case NV3_CHROMA_UNKNOWN_0200:
nv_log("Method Execution: Chroma Unknown 0x0200 0x%08x", param);
nv3_pgraph_interrupt_invalid(NV3_PGRAPH_INTR_1_SOFTWARE_METHOD_PENDING);
break;
case NV3_CHROMA_KEY:
{
nv3_color_expanded_t expanded_color = nv3_render_expand_color(param, grobj);
nv3->pgraph.chroma_key = nv3_render_to_chroma(expanded_color);
nv_log("Method Execution: Chroma = 0x%08x", nv3->pgraph.chroma_key);
break;
}
default:
warning("%s: Invalid or unimplemented method 0x%04x\n", nv3_class_names[context.class_id & 0x1F], method_id);
nv3_pgraph_interrupt_invalid(NV3_PGRAPH_INTR_1_SOFTWARE_METHOD_PENDING);
break;
}
}

View File

@@ -0,0 +1,40 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* NV3: Methods for class 0x02 (Plane mask)
*
*
*
* Authors: Connor Hyde, <mario64crashed@gmail.com> I need a better email address ;^)
*
* Copyright 2024-2025 Connor Hyde
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/pci.h>
#include <86box/rom.h>
#include <86box/video.h>
#include <86box/nv/vid_nv.h>
#include <86box/nv/vid_nv3.h>
void nv3_class_004_method(uint32_t param, uint32_t method_id, nv3_ramin_context_t context, nv3_grobj_t grobj)
{
switch (method_id)
{
default:
warning("%s: Invalid or unimplemented method 0x%04x\n", nv3_class_names[context.class_id & 0x1F], method_id);
nv3_pgraph_interrupt_invalid(NV3_PGRAPH_INTR_1_SOFTWARE_METHOD_PENDING);
return;
}
}

View File

@@ -0,0 +1,49 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* NV3: Methods for class 0x05 (Clipping rectangle)
*
*
*
* Authors: Connor Hyde, <mario64crashed@gmail.com> I need a better email address ;^)
*
* Copyright 2024-2025 Connor Hyde
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/pci.h>
#include <86box/rom.h>
#include <86box/video.h>
#include <86box/nv/vid_nv.h>
#include <86box/nv/vid_nv3.h>
void nv3_class_005_method(uint32_t param, uint32_t method_id, nv3_ramin_context_t context, nv3_grobj_t grobj)
{
switch (method_id)
{
case NV3_CLIP_POSITION:
nv3->pgraph.clip_start.x = (param >> 16) & 0xFFFF;
nv3->pgraph.clip_start.y = (param) & 0xFFFF;
nv_log("Method Execution: Clip Position: %d,%d\n", nv3->pgraph.clip_start.x, nv3->pgraph.clip_start.y);
break;
case NV3_CLIP_SIZE:
nv3->pgraph.clip_size.x = (param >> 16) & 0xFFFF;
nv3->pgraph.clip_size.y = (param) & 0xFFFF;
nv_log("Method Execution: Clip Size: %d,%d\n", nv3->pgraph.clip_start.x, nv3->pgraph.clip_start.y);
break;
default:
warning("%s: Invalid or unimplemented method 0x%04x\n", nv3_class_names[context.class_id & 0x1F], method_id);
nv3_pgraph_interrupt_invalid(NV3_PGRAPH_INTR_1_SOFTWARE_METHOD_PENDING);
return;
}
}

View File

@@ -0,0 +1,86 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* NV3: Methods for class 0x06 (Pattern)
*
*
*
* Authors: Connor Hyde, <mario64crashed@gmail.com> I need a better email address ;^)
*
* Copyright 2024-2025 Connor Hyde
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/pci.h>
#include <86box/rom.h>
#include <86box/video.h>
#include <86box/nv/vid_nv.h>
#include <86box/nv/vid_nv3.h>
void nv3_class_006_method(uint32_t param, uint32_t method_id, nv3_ramin_context_t context, nv3_grobj_t grobj)
{
switch (method_id)
{
/* Valid software method, suppress logging */
case NV3_PATTERN_FORMAT:
nv3_pgraph_interrupt_invalid(NV3_PGRAPH_INTR_1_SOFTWARE_METHOD_PENDING);
break;
case NV3_PATTERN_SHAPE:
/* If the shape is not valid, tell the software that it's invalid */
/*
Technically you are meant to do this:
But in practice, I don't know, because it always submits 0x20 or 0x40, which are valid when param & 0x03,
and appear to be deliberate behaviour in the drivers rather than bugs. What
if (param > NV3_PATTERN_SHAPE_LAST_VALID)
{
warning("NV3 class 0x06 (Pattern) invalid shape %d (This is a bug)", param);
nv3_pgraph_interrupt_invalid(NV3_PGRAPH_INTR_1_INVALID_DATA);
return;
}
*/
nv3->pgraph.pattern_shape = param & 0x03;
break;
/* Seems to be "SetPatternSelect" on Riva TNT and later, but possibly called by accident on Riva 128. There is no hardware equivalent for this. So let's just suppress
the warnings. */
case NV3_PATTERN_UNUSED_DRIVER_BUG:
break;
case NV3_PATTERN_COLOR0:
{
nv3_color_expanded_t expanded_colour0 = nv3_render_expand_color(param, grobj);
nv3_render_set_pattern_color(expanded_colour0, false);
break;
}
case NV3_PATTERN_COLOR1:
{
nv3_color_expanded_t expanded_colour1 = nv3_render_expand_color(param, grobj);
nv3_render_set_pattern_color(expanded_colour1, true);
break;
}
case NV3_PATTERN_BITMAP_HIGH:
nv3->pgraph.pattern_bitmap = 0; //reset
nv3->pgraph.pattern_bitmap |= ((uint64_t)param << 32);
break;
case NV3_PATTERN_BITMAP_LOW:
nv3->pgraph.pattern_bitmap |= param;
break;
default:
warning("%s: Invalid or unimplemented method 0x%04x\n", nv3_class_names[context.class_id & 0x1F], method_id);
nv3_pgraph_interrupt_invalid(NV3_PGRAPH_INTR_1_SOFTWARE_METHOD_PENDING);
break;
}
}

View File

@@ -0,0 +1,69 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* NV3: Methods for class 0x07 (Rectangle)
*
*
*
* Authors: Connor Hyde, <mario64crashed@gmail.com> I need a better email address ;^)
*
* Copyright 2024-2025 Connor Hyde
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/pci.h>
#include <86box/rom.h>
#include <86box/video.h>
#include <86box/nv/vid_nv.h>
#include <86box/nv/vid_nv3.h>
void nv3_class_007_method(uint32_t param, uint32_t method_id, nv3_ramin_context_t context, nv3_grobj_t grobj)
{
switch (method_id)
{
case NV3_RECTANGLE_COLOR:
nv3->pgraph.rectangle.color = param;
break;
default:
/* Check for any rectangle point or size method. */
if (method_id >= NV3_RECTANGLE_START && method_id <= NV3_RECTANGLE_END)
{
uint32_t index = (method_id - NV3_RECTANGLE_START) >> 3;
// If the size is submitted, render it.
if (method_id & 0x04)
{
nv3->pgraph.rectangle.size[index].x = param & 0xFFFF;
nv3->pgraph.rectangle.size[index].y = (param >> 16) & 0xFFFF;
nv_log("Method Execution: Rect%d Size=%d,%d Color=0x%08x\n", index, nv3->pgraph.rectangle.size[index].x, nv3->pgraph.rectangle.size[index].y, nv3->pgraph.rectangle.color);
nv3_render_rect(nv3->pgraph.rectangle.position[index], nv3->pgraph.rectangle.size[index], nv3->pgraph.rectangle.color, grobj);
}
else // position
{
nv3->pgraph.rectangle.position[index].x = param & 0xFFFF;
nv3->pgraph.rectangle.position[index].y = (param >> 16) & 0xFFFF;
nv_log("Method Execution: Rect%d Position=%d,%d\n", index, nv3->pgraph.rectangle.position[index].x, nv3->pgraph.rectangle.position[index].y);
}
return;
}
warning("%s: Invalid or unimplemented method 0x%04x\n", nv3_class_names[context.class_id & 0x1F], method_id);
nv3_pgraph_interrupt_invalid(NV3_PGRAPH_INTR_1_SOFTWARE_METHOD_PENDING);
return;
}
}

View File

@@ -0,0 +1,40 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* NV3: Methods for class 0x08 (Point)
*
*
*
* Authors: Connor Hyde, <mario64crashed@gmail.com> I need a better email address ;^)
*
* Copyright 2024-2025 Connor Hyde
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/pci.h>
#include <86box/rom.h>
#include <86box/video.h>
#include <86box/nv/vid_nv.h>
#include <86box/nv/vid_nv3.h>
void nv3_class_008_method(uint32_t param, uint32_t method_id, nv3_ramin_context_t context, nv3_grobj_t grobj)
{
switch (method_id)
{
default:
warning("%s: Invalid or unimplemented method 0x%04x\n", nv3_class_names[context.class_id & 0x1F], method_id);
nv3_pgraph_interrupt_invalid(NV3_PGRAPH_INTR_1_SOFTWARE_METHOD_PENDING);
return;
}
}

View File

@@ -0,0 +1,40 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* NV3: Methods for class 0x09 (Line)
*
*
*
* Authors: Connor Hyde, <mario64crashed@gmail.com> I need a better email address ;^)
*
* Copyright 2024-2025 Connor Hyde
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/pci.h>
#include <86box/rom.h>
#include <86box/video.h>
#include <86box/nv/vid_nv.h>
#include <86box/nv/vid_nv3.h>
void nv3_class_009_method(uint32_t param, uint32_t method_id, nv3_ramin_context_t context, nv3_grobj_t grobj)
{
switch (method_id)
{
default:
warning("%s: Invalid or unimplemented method 0x%04x\n", nv3_class_names[context.class_id & 0x1F], method_id);
nv3_pgraph_interrupt_invalid(NV3_PGRAPH_INTR_1_SOFTWARE_METHOD_PENDING);
return;
}
}

View File

@@ -0,0 +1,41 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* NV3: Methods for class 0x0A (Lin - a line without starting or ending pixels)
*
*
*
* Authors: Connor Hyde, <mario64crashed@gmail.com> I need a better email address ;^)
*
* Copyright 2024-2025 Connor Hyde
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/pci.h>
#include <86box/rom.h>
#include <86box/video.h>
#include <86box/nv/vid_nv.h>
#include <86box/nv/vid_nv3.h>
void nv3_class_00a_method(uint32_t param, uint32_t method_id, nv3_ramin_context_t context, nv3_grobj_t grobj)
{
switch (method_id)
{
default:
warning("%s: Invalid or unimplemented method 0x%04x\n", nv3_class_names[context.class_id & 0x1F], method_id);
nv3_pgraph_interrupt_invalid(NV3_PGRAPH_INTR_1_SOFTWARE_METHOD_PENDING);
return;
}
}

View File

@@ -0,0 +1,40 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* NV3: Methods for class 0x0B (Basic triangle)
*
*
*
* Authors: Connor Hyde, <mario64crashed@gmail.com> I need a better email address ;^)
*
* Copyright 2024-2025 Connor Hyde
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/pci.h>
#include <86box/rom.h>
#include <86box/video.h>
#include <86box/nv/vid_nv.h>
#include <86box/nv/vid_nv3.h>
void nv3_class_00b_method(uint32_t param, uint32_t method_id, nv3_ramin_context_t context, nv3_grobj_t grobj)
{
switch (method_id)
{
default:
warning("%s: Invalid or unimplemented method 0x%04x\n", nv3_class_names[context.class_id & 0x1F], method_id);
nv3_pgraph_interrupt_invalid(NV3_PGRAPH_INTR_1_SOFTWARE_METHOD_PENDING);
return;
}
}

View File

@@ -0,0 +1,285 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* NV3: Methods for class 0x0C (Windows 95 GDI text acceleration)
*
*
*
* Authors: Connor Hyde, <mario64crashed@gmail.com> I need a better email address ;^)
*
* Copyright 2024-2025 Connor Hyde
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/pci.h>
#include <86box/rom.h>
#include <86box/video.h>
#include <86box/nv/vid_nv.h>
#include <86box/nv/vid_nv3.h>
void nv3_class_00c_method(uint32_t param, uint32_t method_id, nv3_ramin_context_t context, nv3_grobj_t grobj)
{
switch (method_id)
{
/* Type A: Unclipped Rectangle */
/* NOTE: This method is used by the GDI driver as part of the notification engine. */
case NV3_W95TXT_A_COLOR:
nv3->pgraph.win95_gdi_text.color_a = param;
nv_log("Method Execution: GDI-A Color 0x%08x\n", nv3->pgraph.win95_gdi_text.color_a);
break;
/* Type B: Clipped Rectangle */
case NV3_W95TXT_B_COLOR:
nv3->pgraph.win95_gdi_text.color_b = param;
nv_log("Method Execution: GDI-B Color 0x%08x\n", nv3->pgraph.win95_gdi_text.color_b);
break;
case NV3_W95TXT_B_CLIP_TOPLEFT:
nv3->pgraph.win95_gdi_text.clip_b.left = (param & 0xFFFF);
nv3->pgraph.win95_gdi_text.clip_b.top = ((param >> 16) & 0xFFFF);
nv_log("Method Execution: GDI-B Clip Left,Top %04x,%04x", nv3->pgraph.win95_gdi_text.clip_b.left, nv3->pgraph.win95_gdi_text.clip_b.top);
break;
case NV3_W95TXT_B_CLIP_BOTTOMRIGHT:
nv3->pgraph.win95_gdi_text.clip_b.bottom = (param & 0xFFFF);
nv3->pgraph.win95_gdi_text.clip_b.right = ((param >> 16) & 0xFFFF);
nv_log("Method Execution: GDI-B Clip Bottom,Right %04x,%04x", nv3->pgraph.win95_gdi_text.clip_b.right, nv3->pgraph.win95_gdi_text.clip_b.bottom);
break;
/* Type C: Unclipped Bitmap */
case NV3_W95TXT_C_CLIP_COLOR:
nv3->pgraph.win95_gdi_text.color1_c = param;
nv_log("Method Execution: GDI-C Color 0x%08x\n", nv3->pgraph.win95_gdi_text.color1_c);
break;
case NV3_W95TXT_C_CLIP_SIZE:
nv3->pgraph.win95_gdi_text.size_c.x = (param & 0xFFFF);
nv3->pgraph.win95_gdi_text.size_c.y = ((param >> 16) & 0xFFFF);
nv3->pgraph.win95_gdi_text_bit_count = 0;
nv_log("Method Execution: GDI-C Size In %04x,%04x\n", nv3->pgraph.win95_gdi_text.size_c.x, nv3->pgraph.win95_gdi_text.size_c.y);
break;
case NV3_W95TXT_C_CLIP_POSITION:
nv3->pgraph.win95_gdi_text.point_c.x = (param & 0xFFFF);
nv3->pgraph.win95_gdi_text.point_c.y = ((param >> 16) & 0xFFFF);
nv_log("Method Execution: GDI-C Point %04x,%04x\n", nv3->pgraph.win95_gdi_text.point_c.x, nv3->pgraph.win95_gdi_text.point_c.y);
nv3->pgraph.win95_gdi_text_current_position.x = nv3->pgraph.win95_gdi_text.point_c.x ;
nv3->pgraph.win95_gdi_text_current_position.y = nv3->pgraph.win95_gdi_text.point_c.y;
break;
case NV3_W95TXT_C_CLIP_TOPLEFT:
nv3->pgraph.win95_gdi_text.clip_c.left = (param & 0xFFFF);
nv3->pgraph.win95_gdi_text.clip_c.top = ((param >> 16) & 0xFFFF);
nv_log("Method Execution: GDI-C Clip Left,Top %04x,%04x\n", nv3->pgraph.win95_gdi_text.clip_c.left, nv3->pgraph.win95_gdi_text.clip_c.top);
break;
case NV3_W95TXT_C_CLIP_BOTTOMRIGHT:
nv3->pgraph.win95_gdi_text.clip_c.right = (param & 0xFFFF);
nv3->pgraph.win95_gdi_text.clip_c.bottom = ((param >> 16) & 0xFFFF);
/* is it "only if we are out of the top left or the bottom right or is it "all of them"*/
nv_log("Method Execution: GDI-C Clip Right,Bottom %04x,%04x\n", nv3->pgraph.win95_gdi_text.clip_c.left, nv3->pgraph.win95_gdi_text.clip_c.top);
break;
/* Type B and C not implemented YET, as they are not used by NT GDI driver */
case NV3_W95TXT_D_CLIP_TOPLEFT:
nv3->pgraph.win95_gdi_text.clip_d.left = (param & 0xFFFF);
nv3->pgraph.win95_gdi_text.clip_d.top = ((param >> 16) & 0xFFFF);
nv_log("Method Execution: GDI-D Clip Left,Top %04x,%04x\n", nv3->pgraph.win95_gdi_text.clip_d.left, nv3->pgraph.win95_gdi_text.clip_d.top);
break;
case NV3_W95TXT_D_CLIP_BOTTOMRIGHT:
nv3->pgraph.win95_gdi_text.clip_d.right = (param & 0xFFFF);
nv3->pgraph.win95_gdi_text.clip_d.bottom = ((param >> 16) & 0xFFFF);
/* is it "only if we are out of the top left or the bottom right or is it "all of them"*/
nv_log("Method Execution: GDI-D Clip Right,Bottom %04x,%04x\n", nv3->pgraph.win95_gdi_text.clip_d.left, nv3->pgraph.win95_gdi_text.clip_d.top);
break;
case NV3_W95TXT_D_CLIP_COLOR:
nv3->pgraph.win95_gdi_text.color1_d = param;
nv_log("Method Execution: GDI-D Color 0x%08x\n", nv3->pgraph.win95_gdi_text.color_a);
break;
case NV3_W95TXT_D_CLIP_SIZE_IN:
nv3->pgraph.win95_gdi_text.size_in_d.x = (param & 0xFFFF);
nv3->pgraph.win95_gdi_text.size_in_d.y = ((param >> 16) & 0xFFFF);
nv_log("Method Execution: GDI-D Size In %04x,%04x\n", nv3->pgraph.win95_gdi_text.size_in_d.x, nv3->pgraph.win95_gdi_text.size_in_d.y);
break;
case NV3_W95TXT_D_CLIP_SIZE_OUT:
nv3->pgraph.win95_gdi_text.size_out_d.x = (param & 0xFFFF);
nv3->pgraph.win95_gdi_text.size_out_d.y = ((param >> 16) & 0xFFFF);
nv_log("Method Execution: GDI-D Size Out %04x,%04x\n", nv3->pgraph.win95_gdi_text.size_out_d.x, nv3->pgraph.win95_gdi_text.size_out_d.y);
break;
case NV3_W95TXT_D_CLIP_POSITION:
nv3->pgraph.win95_gdi_text.point_d.x = (param & 0xFFFF);
nv3->pgraph.win95_gdi_text.point_d.y = ((param >> 16) & 0xFFFF);
nv_log("Method Execution: GDI-D Point %04x,%04x\n", nv3->pgraph.win95_gdi_text.point_d.x, nv3->pgraph.win95_gdi_text.point_d.y);
nv3->pgraph.win95_gdi_text_current_position.x = nv3->pgraph.win95_gdi_text.point_d.x;
nv3->pgraph.win95_gdi_text_current_position.y = nv3->pgraph.win95_gdi_text.point_d.y;
nv3->pgraph.win95_gdi_text_bit_count = 0;
break;
/* Type E: Two-colour 1bpp */
case NV3_W95TXT_E_CLIP_TOPLEFT:
nv3->pgraph.win95_gdi_text.clip_e.left = (param & 0xFFFF);
nv3->pgraph.win95_gdi_text.clip_e.top = ((param >> 16) & 0xFFFF);
nv_log("Method Execution: GDI-E Clip Left,Top 0x%08x\n", nv3->pgraph.win95_gdi_text.clip_e.left, nv3->pgraph.win95_gdi_text.clip_e.top);
break;
case NV3_W95TXT_E_CLIP_BOTTOMRIGHT:
nv3->pgraph.win95_gdi_text.clip_e.right = (param & 0xFFFF);
nv3->pgraph.win95_gdi_text.clip_e.bottom = ((param >> 16) & 0xFFFF);
nv_log("Method Execution: GDI-E Clip Bottom,Right 0x%08x\n", nv3->pgraph.win95_gdi_text.clip_e.right, nv3->pgraph.win95_gdi_text.clip_e.bottom);
/* is it "only if we are out of the top left or the bottom right or is it "all of them"*/
break;
case NV3_W95TXT_E_CLIP_COLOR_0:
nv3->pgraph.win95_gdi_text.color0_e = param;
nv_log("Method Execution: GDI-E Color0 0x%08x\n", nv3->pgraph.win95_gdi_text.color_a);
break;
case NV3_W95TXT_E_CLIP_COLOR_1:
nv3->pgraph.win95_gdi_text.color1_e = param;
nv_log("Method Execution: GDI-E Color1 0x%08x\n", nv3->pgraph.win95_gdi_text.color_a);
break;
case NV3_W95TXT_E_CLIP_SIZE_IN:
nv3->pgraph.win95_gdi_text.size_in_e.x = (param & 0xFFFF);
nv3->pgraph.win95_gdi_text.size_in_e.y = ((param >> 16) & 0xFFFF);
nv_log("Method Execution: GDI-E Size In %04x,%04x\n", nv3->pgraph.win95_gdi_text.size_in_e.x, nv3->pgraph.win95_gdi_text.size_in_e.y);
break;
case NV3_W95TXT_E_CLIP_SIZE_OUT:
nv3->pgraph.win95_gdi_text.size_out_e.x = (param & 0xFFFF);
nv3->pgraph.win95_gdi_text.size_out_e.y = ((param >> 16) & 0xFFFF);
nv_log("Method Execution: GDI-E Size Out %04x,%04x\n", nv3->pgraph.win95_gdi_text.size_out_e.x, nv3->pgraph.win95_gdi_text.size_out_e.y);
break;
case NV3_W95TXT_E_CLIP_POSITION:
nv3->pgraph.win95_gdi_text.point_e.x = (param & 0xFFFF);
nv3->pgraph.win95_gdi_text.point_e.y = ((param >> 16) & 0xFFFF);
nv3->pgraph.win95_gdi_text_current_position.x = nv3->pgraph.win95_gdi_text.point_e.x;
nv3->pgraph.win95_gdi_text_current_position.y = nv3->pgraph.win95_gdi_text.point_e.y;
nv3->pgraph.win95_gdi_text_bit_count = 0;
nv_log("Method Execution: GDI-E Point %04x,%04x\n", nv3->pgraph.win95_gdi_text.point_e.x, nv3->pgraph.win95_gdi_text.point_e.y);
break;
default:
/* Type A submission: these are the same things as rectangles */
if (method_id >= NV3_W95TXT_A_RECT_START && method_id <= NV3_W95TXT_A_RECT_END)
{
uint32_t index = (method_id - NV3_W95TXT_A_RECT_START) >> 3;
// IN THIS ONE SPECIFIC PLACE, ****AND ONLY THIS ONE SPECIFIC PLACE****, X AND Y ARE SWAPPED???? */
// If the size is submitted, render it.
if (method_id & 0x04)
{
nv3->pgraph.win95_gdi_text.rect_a_size[index].x = (param >> 16) & 0xFFFF;
nv3->pgraph.win95_gdi_text.rect_a_size[index].y = param & 0xFFFF;
nv_log("Method Execution: Rect GDI-A%d Size=%d,%d", index, nv3->pgraph.win95_gdi_text.rect_a_size[index].x,
nv3->pgraph.win95_gdi_text.rect_a_size[index].y);
nv3_render_rect(nv3->pgraph.win95_gdi_text.rect_a_position[index],
nv3->pgraph.win95_gdi_text.rect_a_size[index], nv3->pgraph.win95_gdi_text.color_a, grobj);
}
else // position
{
nv3->pgraph.win95_gdi_text.rect_a_position[index].x = (param >> 16) & 0xFFFF;
nv3->pgraph.win95_gdi_text.rect_a_position[index].y = param & 0xFFFF;
nv_log("Method Execution: Rect GDI-A%d Position=%d,%d\n", index,
nv3->pgraph.win95_gdi_text.rect_a_position[index].x, nv3->pgraph.win95_gdi_text.rect_a_position[index].y);
}
return;
}
/* Type B: Clipped Rectangle */
else if (method_id >= NV3_W95TXT_B_CLIP_CLIPRECT_START && method_id <= NV3_W95TXT_B_CLIP_CLIPRECT_END)
{
uint32_t index = (method_id - NV3_W95TXT_B_CLIP_CLIPRECT_START) >> 3;
/* Works slightly differently, we define the bounds of the rectangle instead. */
if (method_id & 0x04)
{
nv3->pgraph.win95_gdi_text.clipped_rect[index].right = (param & 0xFFFF);
nv3->pgraph.win95_gdi_text.clipped_rect[index].bottom = ((param >> 16) & 0xFFFF);
nv_log("Method Execution: Rect GDI-B%d Right,Bottom=%d,%d\n", index, nv3->pgraph.win95_gdi_text.clipped_rect[index].right,
nv3->pgraph.win95_gdi_text.clipped_rect[index].bottom);
nv3_render_rect_clipped(nv3->pgraph.win95_gdi_text.clipped_rect[index],
nv3->pgraph.win95_gdi_text.color_b, grobj);
}
else // left,top
{
nv3->pgraph.win95_gdi_text.clipped_rect[index].left = (param & 0xFFFF);
nv3->pgraph.win95_gdi_text.clipped_rect[index].top = ((param >> 16) & 0xFFFF);
nv_log("Method Execution: Rect GDI-B%d Left,Top=%d,%d\n", index,
nv3->pgraph.win95_gdi_text.clipped_rect[index].left, nv3->pgraph.win95_gdi_text.clipped_rect[index].top);
}
return;
}
/* Type C */
else if (method_id >= NV3_W95TXT_C_CLIP_CLIPRECT_START && method_id <= NV3_W95TXT_C_CLIP_CLIPRECT_END)
{
/* lol */
uint32_t index = (method_id - NV3_W95TXT_C_CLIP_CLIPRECT_START) >> 3;
nv3->pgraph.win95_gdi_text.bitmap_c[index] = param;
/* Mammoth logger! */
nv_log("Method Execution: Rect GDI-C%d Data=%08x Size%04x,%04x Point%04x,%04x Color=%08x Clip Left=0x%04x Right=0x%04x Top=0x%04x Bottom=0x%04x",
index, param, nv3->pgraph.win95_gdi_text.size_c.x, nv3->pgraph.win95_gdi_text.size_c.y,
nv3->pgraph.win95_gdi_text.point_c.x, nv3->pgraph.win95_gdi_text.point_c.y,
nv3->pgraph.win95_gdi_text.color1_c,
nv3->pgraph.win95_gdi_text.clip_c.left, nv3->pgraph.win95_gdi_text.clip_c.right,
nv3->pgraph.win95_gdi_text.clip_c.top, nv3->pgraph.win95_gdi_text.clip_c.bottom);
nv3_render_gdi_transparent_bitmap(false, nv3->pgraph.win95_gdi_text.color1_c, nv3->pgraph.win95_gdi_text.bitmap_c[index], grobj);
return;
}
else if (method_id >= NV3_W95TXT_D_CLIP_CLIPRECT_START && method_id <= NV3_W95TXT_D_CLIP_CLIPRECT_END)
{
/* lol */
uint32_t index = (method_id - NV3_W95TXT_D_CLIP_CLIPRECT_START) >> 3;
nv3->pgraph.win95_gdi_text.bitmap_d[index] = param;
/* Mammoth logger! */
nv_log("Method Execution: Rect GDI-D%d Data=%08x SizeIn%04x,%04x SizeOut%04x,%04x Point%04x,%04x Color=%08x Clip Left=0x%04x Right=0x%04x Top=0x%04x Bottom=0x%04x",
index, param, nv3->pgraph.win95_gdi_text.size_in_d.x, nv3->pgraph.win95_gdi_text.size_in_d.y,
nv3->pgraph.win95_gdi_text.size_out_d.x, nv3->pgraph.win95_gdi_text.size_out_d.y,
nv3->pgraph.win95_gdi_text.point_d.x, nv3->pgraph.win95_gdi_text.point_d.y,
nv3->pgraph.win95_gdi_text.color1_d,
nv3->pgraph.win95_gdi_text.clip_d.left, nv3->pgraph.win95_gdi_text.clip_d.right,
nv3->pgraph.win95_gdi_text.clip_d.top, nv3->pgraph.win95_gdi_text.clip_d.bottom);
nv3_render_gdi_transparent_bitmap(true, nv3->pgraph.win95_gdi_text.color1_d, nv3->pgraph.win95_gdi_text.bitmap_d[index], grobj);
return;
}
else if (method_id >= NV3_W95TXT_E_CLIP_CLIPRECT_START && method_id <= NV3_W95TXT_E_CLIP_CLIPRECT_END)
{
/* lol */
uint32_t index = (method_id - NV3_W95TXT_E_CLIP_CLIPRECT_START) >> 3;
nv3->pgraph.win95_gdi_text.bitmap_e[index] = param;
/* Mammoth logger! */
nv_log("Method Execution: Rect GDI-E%d Data=%08x SizeIn%04x,%04x SizeOut%04x,%04x Point%04x,%04x Color=%08x Clip Left=0x%04x Right=0x%04x Top=0x%04x Bottom=0x%04x",
index, param, nv3->pgraph.win95_gdi_text.size_in_e.x, nv3->pgraph.win95_gdi_text.size_in_e.y,
nv3->pgraph.win95_gdi_text.size_out_e.x, nv3->pgraph.win95_gdi_text.size_out_e.y,
nv3->pgraph.win95_gdi_text.point_e.x, nv3->pgraph.win95_gdi_text.point_e.y,
nv3->pgraph.win95_gdi_text.color1_e,
nv3->pgraph.win95_gdi_text.clip_e.left, nv3->pgraph.win95_gdi_text.clip_e.right, nv3->pgraph.win95_gdi_text.clip_e.top, nv3->pgraph.win95_gdi_text.clip_e.bottom);
nv3_render_gdi_1bpp_bitmap(nv3->pgraph.win95_gdi_text.color0_e, nv3->pgraph.win95_gdi_text.color1_e, nv3->pgraph.win95_gdi_text.bitmap_e[index], grobj);
return;
}
warning("%s: Invalid or unimplemented method 0x%04x\n", nv3_class_names[context.class_id & 0x1F], method_id);
nv3_pgraph_interrupt_invalid(NV3_PGRAPH_INTR_1_SOFTWARE_METHOD_PENDING);
break;
}
}

View File

@@ -0,0 +1,94 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* NV3: Methods for class 0x0D (Reformat image in memory)
*
*
*
* Authors: Connor Hyde, <mario64crashed@gmail.com> I need a better email address ;^)
*
* Copyright 2024-2025 Connor Hyde
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/pci.h>
#include <86box/rom.h>
#include <86box/video.h>
#include <86box/nv/vid_nv.h>
#include <86box/nv/vid_nv3.h>
void nv3_class_00d_method(uint32_t param, uint32_t method_id, nv3_ramin_context_t context, nv3_grobj_t grobj)
{
switch (method_id)
{
case NV3_M2MF_IN_CTXDMA_OFFSET:
nv3->pgraph.m2mf.offset_in = param;
nv_log("Method Execution: M2MF Offset In = 0x%08x", param);
break;
case NV3_M2MF_OUT_CTXDMA_OFFSET:
nv3->pgraph.m2mf.offset_out = param;
nv_log("Method Execution: M2MF Offset Out = 0x%08x", param);
break;
case NV3_M2MF_IN_PITCH:
nv3->pgraph.m2mf.pitch_in = param;
nv_log("Method Execution: M2MF Pitch In = 0x%08x", param);
break;
case NV3_M2MF_OUT_PITCH:
nv3->pgraph.m2mf.pitch_out = param;
nv_log("Method Execution: M2MF Pitch Out = 0x%08x", param);
break;
case NV3_M2MF_SCANLINE_LENGTH_IN_BYTES:
nv3->pgraph.m2mf.scanline_length = param;
nv_log("Method Execution: M2MF Scanline Length in Bytes = 0x%08x", param);
break;
case NV3_M2MF_NUM_SCANLINES:
nv3->pgraph.m2mf.num_scanlines = param;
nv_log("Method Execution: M2MF Num Scanlines = 0x%08x", param);
break;
case NV3_M2MF_FORMAT:
nv3->pgraph.m2mf.format = param;
nv_log("Method Execution: M2MF Format = 0x%08x", param);
// Format Done - start m2mf
nv3_perform_dma_m2mf(grobj);
break;
case NV3_M2MF_NOTIFY:
/* This is technically its own thing, but I don't know if it's ever a problem with how we've designed it */
if (nv3->pgraph.notify_pending)
{
nv_log("WARNING: M2MF notification with notify_pending already set. param=0x%08x, method=0x%04x, grobj=0x%08x 0x%08x 0x%08x 0x%08x\n");
nv_log("IF THIS BUILD WAS COMPILED WITH NV_LOG_ENABLE_ULTRA, YOU SHOULD SEE A CONTEXT BELOW");
nv3_debug_ramin_print_context_info(param, context);
nv3_pgraph_interrupt_invalid(NV3_PGRAPH_INTR_1_DOUBLE_NOTIFY);
// disable
nv3->pgraph.notify_pending = false;
nv3_pgraph_interrupt_invalid(NV3_PGRAPH_INTR_1_DOUBLE_NOTIFY);
/* may need to disable fifo in this state */
return;
}
nv_log("Method Execution: TODO: ACTUALLY IMPLEMENT M2MF!!!!");
// set a notify as pending.
nv3->pgraph.notifier = param;
nv3->pgraph.notify_pending = true;
break;
default:
warning("%s: Invalid or unimplemented method 0x%04x\n", nv3_class_names[context.class_id & 0x1F], method_id);
nv3_pgraph_interrupt_invalid(NV3_PGRAPH_INTR_1_SOFTWARE_METHOD_PENDING);
break;;
}
}

View File

@@ -0,0 +1,40 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* NV3: Methods for class 0x0E (Get image from vram and scale it)
*
*
*
* Authors: Connor Hyde, <mario64crashed@gmail.com> I need a better email address ;^)
*
* Copyright 2024-2025 Connor Hyde
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/pci.h>
#include <86box/rom.h>
#include <86box/video.h>
#include <86box/nv/vid_nv.h>
#include <86box/nv/vid_nv3.h>
void nv3_class_00e_method(uint32_t param, uint32_t method_id, nv3_ramin_context_t context, nv3_grobj_t grobj)
{
switch (method_id)
{
default:
warning("%s: Invalid or unimplemented method 0x%04x\n", nv3_class_names[context.class_id & 0x1F], method_id);
nv3_pgraph_interrupt_invalid(NV3_PGRAPH_INTR_1_SOFTWARE_METHOD_PENDING);
return;
}
}

View File

@@ -0,0 +1,60 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* NV3: Methods for class 0x10 (Blit something)
*
*
*
* Authors: Connor Hyde, <mario64crashed@gmail.com> I need a better email address ;^)
*
* Copyright 2024-2025 Connor Hyde
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/pci.h>
#include <86box/rom.h>
#include <86box/video.h>
#include <86box/nv/vid_nv.h>
#include <86box/nv/vid_nv3.h>
void nv3_class_010_method(uint32_t param, uint32_t method_id, nv3_ramin_context_t context, nv3_grobj_t grobj)
{
switch (method_id)
{
case NV3_BLIT_POSITION_IN:
nv3->pgraph.blit.point_in.x = (param & 0xFFFF);
nv3->pgraph.blit.point_in.y = ((param >> 16) & 0xFFFF);
nv_log("Method Execution: S2SB POINT_IN %d,%d\n", nv3->pgraph.blit.point_in.x, nv3->pgraph.blit.point_in.y);
break;
case NV3_BLIT_POSITION_OUT:
nv3->pgraph.blit.point_out.x = (param & 0xFFFF);
nv3->pgraph.blit.point_out.y = ((param >> 16) & 0xFFFF);
nv_log("Method Execution: S2SB POINT_OUT %d,%d\n", nv3->pgraph.blit.point_out.x, nv3->pgraph.blit.point_out.y);
break;
case NV3_BLIT_SIZE:
/* This is the last one*/
nv3->pgraph.blit.size.x = (param & 0xFFFF);
nv3->pgraph.blit.size.y = ((param >> 16) & 0xFFFF);
nv_log("Method Execution: S2SB Size %d,%d grobj_0=0x%08x\n", nv3->pgraph.blit.size.x, nv3->pgraph.blit.size.y, grobj.grobj_0);
nv3_render_blit_screen2screen(grobj);
break;
default:
warning("%s: Invalid or unimplemented method 0x%04x\n", nv3_class_names[context.class_id & 0x1F], method_id);
nv3_pgraph_interrupt_invalid(NV3_PGRAPH_INTR_1_SOFTWARE_METHOD_PENDING);
return;
}
}

View File

@@ -0,0 +1,67 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* NV3: Methods for class 0x11 (Color image)
*
*
*
* Authors: Connor Hyde, <mario64crashed@gmail.com> I need a better email address ;^)
*
* Copyright 2024-2025 Connor Hyde
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/pci.h>
#include <86box/rom.h>
#include <86box/video.h>
#include <86box/nv/vid_nv.h>
#include <86box/nv/vid_nv3.h>
void nv3_class_011_method(uint32_t param, uint32_t method_id, nv3_ramin_context_t context, nv3_grobj_t grobj)
{
switch (method_id)
{
case NV3_IMAGE_START_POSITION:
nv3->pgraph.image.point.x = (param & 0xFFFF);
nv3->pgraph.image.point.y = (param >> 16);
nv_log("Method Execution: Image Point=%d,%d\n", nv3->pgraph.image.point.x, nv3->pgraph.image.point.y);
break;
/* Seems to allow scaling of the bitblt. */
case NV3_IMAGE_SIZE:
nv3->pgraph.image.size.x = (param & 0xFFFF);
nv3->pgraph.image.size.y = (param >> 16);
nv_log("Method Execution: Image Size (Clip)=%d,%d\n", nv3->pgraph.image.size.x, nv3->pgraph.image.size.y);
break;
case NV3_IMAGE_SIZE_IN:
nv3->pgraph.image.size_in.x = (param & 0xFFFF);
nv3->pgraph.image.size_in.y = (param >> 16);
nv3->pgraph.image_current_position = nv3->pgraph.image.point;
nv_log("Method Execution: Image SizeIn=%d,%d\n", nv3->pgraph.image.size_in.x, nv3->pgraph.image.size_in.y);
break;
default:
if (method_id >= NV3_IMAGE_COLOR_START && method_id <= NV3_IMAGE_COLOR_END)
{
uint32_t pixel_slot = (method_id - NV3_IMAGE_COLOR_START) >> 2;
nv_log("Method Execution: Image Pixel%d Colour%08x Format%x\n", pixel_slot, param, (grobj.grobj_0) & 0x07);
nv3_render_blit_image(param, grobj);
}
else
{
warning("%s: Invalid or unimplemented method 0x%04x\n", nv3_class_names[context.class_id & 0x1F], method_id);
nv3_pgraph_interrupt_invalid(NV3_PGRAPH_INTR_1_SOFTWARE_METHOD_PENDING);
}
return;
}
}

View File

@@ -0,0 +1,41 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* NV3: Methods for class 0x12 (Monochrome bitmap)
*
*
*
* Authors: Connor Hyde, <mario64crashed@gmail.com> I need a better email address ;^)
*
* Copyright 2024-2025 Connor Hyde
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/pci.h>
#include <86box/rom.h>
#include <86box/video.h>
#include <86box/nv/vid_nv.h>
#include <86box/nv/vid_nv3.h>
void nv3_class_012_method(uint32_t param, uint32_t method_id, nv3_ramin_context_t context, nv3_grobj_t grobj)
{
switch (method_id)
{
default:
warning("%s: Invalid or unimplemented method 0x%04x\n", nv3_class_names[context.class_id & 0x1F], method_id);
nv3_pgraph_interrupt_invalid(NV3_PGRAPH_INTR_1_SOFTWARE_METHOD_PENDING);
return;
}
}

View File

@@ -0,0 +1,41 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* NV3: Methods for class 0x14 (Transfer to Memory)
*
*
*
* Authors: Connor Hyde, <mario64crashed@gmail.com> I need a better email address ;^)
*
* Copyright 2024-2025 Connor Hyde
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/pci.h>
#include <86box/rom.h>
#include <86box/video.h>
#include <86box/nv/vid_nv.h>
#include <86box/nv/vid_nv3.h>
void nv3_class_014_method(uint32_t param, uint32_t method_id, nv3_ramin_context_t context, nv3_grobj_t grobj)
{
switch (method_id)
{
default:
warning("%s: Invalid or unimplemented method 0x%04x\n", nv3_class_names[context.class_id & 0x1F], method_id);
nv3_pgraph_interrupt_invalid(NV3_PGRAPH_INTR_1_SOFTWARE_METHOD_PENDING);
return;
}
}

View File

@@ -0,0 +1,40 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* NV3: Methods for class 0x15 (stretched image from cpu to memory)
*
*
*
* Authors: Connor Hyde, <mario64crashed@gmail.com> I need a better email address ;^)
*
* Copyright 2024-2025 Connor Hyde
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/pci.h>
#include <86box/rom.h>
#include <86box/video.h>
#include <86box/nv/vid_nv.h>
#include <86box/nv/vid_nv3.h>
void nv3_class_015_method(uint32_t param, uint32_t method_id, nv3_ramin_context_t context, nv3_grobj_t grobj)
{
switch (method_id)
{
default:
warning("%s: Invalid or unimplemented method 0x%04x\n", nv3_class_names[context.class_id & 0x1F], method_id);
nv3_pgraph_interrupt_invalid(NV3_PGRAPH_INTR_1_SOFTWARE_METHOD_PENDING);
return;
}
}

View File

@@ -0,0 +1,40 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* NV3: Methods for class 0x17 (Direct3D 5.0 accelerated triangle with zeta buffer)
*
*
*
* Authors: Connor Hyde, <mario64crashed@gmail.com> I need a better email address ;^)
*
* Copyright 2024-2025 Connor Hyde
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/pci.h>
#include <86box/rom.h>
#include <86box/video.h>
#include <86box/nv/vid_nv.h>
#include <86box/nv/vid_nv3.h>
void nv3_class_017_method(uint32_t param, uint32_t method_id, nv3_ramin_context_t context, nv3_grobj_t grobj)
{
switch (method_id)
{
default:
warning("%s: Invalid or unimplemented method 0x%04x\n", nv3_class_names[context.class_id & 0x1F], method_id);
nv3_pgraph_interrupt_invalid(NV3_PGRAPH_INTR_1_SOFTWARE_METHOD_PENDING);
return;
}
}

View File

@@ -0,0 +1,42 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* NV3: Methods for class 0x18 (Point with zeta buffer)
*
*
*
* Authors: Connor Hyde, <mario64crashed@gmail.com> I need a better email address ;^)
*
* Copyright 2024-2025 Connor Hyde
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/pci.h>
#include <86box/rom.h>
#include <86box/video.h>
#include <86box/nv/vid_nv.h>
#include <86box/nv/vid_nv3.h>
struct nv3_object_class_018 nv3_d3d5_point_zeta_buffer;
void nv3_class_018_method(uint32_t param, uint32_t method_id, nv3_ramin_context_t context, nv3_grobj_t grobj)
{
switch (method_id)
{
default:
warning("%s: Invalid or unimplemented method 0x%04x\n", nv3_class_names[context.class_id & 0x1F], method_id);
nv3_pgraph_interrupt_invalid(NV3_PGRAPH_INTR_1_SOFTWARE_METHOD_PENDING);
return;
}
}

View File

@@ -0,0 +1,99 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* NV3: Methods for class 0x1C (Image in memory)
*
*
*
* Authors: Connor Hyde, <mario64crashed@gmail.com> I need a better email address ;^)
*
* Copyright 2024-2025 Connor Hyde
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/pci.h>
#include <86box/rom.h>
#include <86box/video.h>
#include <86box/nv/vid_nv.h>
#include <86box/nv/vid_nv3.h>
void nv3_class_01c_method(uint32_t param, uint32_t method_id, nv3_ramin_context_t context, nv3_grobj_t grobj)
{
/* We need this for a lot of methods, so may as well store it here. */
uint32_t src_buffer_id = (grobj.grobj_0 >> NV3_PGRAPH_CTX_SWITCH_SRC_BUFFER) & 0x03;
switch (method_id)
{
/* Color format of the image */
case NV3_IMAGE_IN_MEMORY_COLOR_FORMAT:
{
// convert to how the bpixel registers represent surface
uint32_t real_format = 1;
/* TODO: THIS CODE MIGHT BE NONSENSE
Convert between different internal representations of the pixel format, because Nvidia says: I WANT TO MAKE YOUR LIFE PAIN.
*/
switch (param)
{
case nv3_image_in_memory_pixel_format_x8g8b8r8:
real_format = 3; //32bit
// no change
break;
case nv3_image_in_memory_pixel_format_x1r5g5b5_p2:
real_format = 2;
break;
case nv3_image_in_memory_pixel_format_le_y16_p2:
real_format = 0;
break;
}
/* Set the format */
nv3->pgraph.bpixel[src_buffer_id] = (real_format | NV3_BPIXEL_FORMAT_IS_VALID);
nv_log("Method Execution: Image in Memory BUF%d COLOR_FORMAT=0x%04x\n", src_buffer_id, param);
break;
}
/* DOn't log invalid */
case NV3_IMAGE_IN_MEMORY_IN_MEMORY_DMA_CTX_TYPE:
nv3_pgraph_interrupt_invalid(NV3_PGRAPH_INTR_1_SOFTWARE_METHOD_PENDING);
break;
/* Pitch - length between scanlines */
case NV3_IMAGE_IN_MEMORY_PITCH:
nv3->pgraph.image_in_memory.pitch = param & 0x1FF0;
nv3->pgraph.bpitch[src_buffer_id] = param & 0x1FF0; // 12:0
nv_log("Method Execution: Image in Memory BUF%d PITCH=0x%04x\n", src_buffer_id, nv3->pgraph.bpitch[src_buffer_id]);
break;
/* Byte offset in GPU VRAM of top left pixel (22:0) */
case NV3_IMAGE_IN_MEMORY_TOP_LEFT_OFFSET:
if (nv3->nvbase.gpu_revision == NV3_PCI_CFG_REVISION_C00) // RIVA 128ZX
nv3->pgraph.boffset[src_buffer_id] = param & 0x7FFFFF;
else
nv3->pgraph.boffset[src_buffer_id] = param & 0x3FFFFF;
nv_log("Method Execution: Image in Memory BUF%d TOP_LEFT_OFFSET=0x%08x\n", src_buffer_id, nv3->pgraph.boffset[src_buffer_id]);
break;
case NV3_NVCLASS_CRAP_START ... NV3_NVCLASS_CRAP_END:
/* Suppress but don't do anything */
nv3_pgraph_interrupt_invalid(NV3_PGRAPH_INTR_1_SOFTWARE_METHOD_PENDING);
break;
default:
warning("%s: Invalid or unimplemented method 0x%04x\n", nv3_class_names[context.class_id & 0x1F], method_id);
nv3_pgraph_interrupt_invalid(NV3_PGRAPH_INTR_1_SOFTWARE_METHOD_PENDING);
break;
}
}

View File

@@ -0,0 +1,67 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* NV3: Defines core class names for debugging purposes
*
*
*
* Authors: Connor Hyde, <mario64crashed@gmail.com> I need a better email address ;^)
*
* Copyright 2024-2025 Connor Hyde
*/
#include <stdio.h>
#include <stdint.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/pci.h>
#include <86box/rom.h> // DEPENDENT!!!
#include <86box/video.h>
#include <86box/nv/vid_nv.h>
#include <86box/nv/vid_nv3.h>
/* These are the object classes AS RECOGNISED BY THE GRAPHICS HARDWARE. */
/* The drivers implement a COMPLETELY DIFFERENT SET OF CLASSES. */
/* THERE CAN ONLY BE 32 CLASSES IN NV3 BECAUSE THE CLASS ID PART OF THE CONTEXT OF A GRAPHICS OBJECT IN PFIFO RAM HASH TABLE IS ONLY 5 BITS LONG! */
const char* nv3_class_names[] =
{
"NV3 INVALID class 0x00",
"NV3 class 0x01: Beta factor",
"NV3 class 0x02: Render operation",
"NV3 class 0x03: Chroma key",
"NV3 class 0x04: Plane mask",
"NV3 class 0x05: Clipping rectangle",
"NV3 class 0x06: Pattern",
"NV3 class 0x07: Rectangle",
"NV3 class 0x08: Point",
"NV3 class 0x09: Line",
"NV3 class 0x0A: Lin (line without starting or ending pixel)",
"NV3 class 0x0B: Triangle",
"NV3 class 0x0C: Windows 95 GDI text acceleration",
"NV3 class 0x0D: Memory to memory format",
"NV3 class 0x0E: Scaled image from memory",
"NV3 INVALID class 0x0F",
"NV3 class 0x10: Blit",
"NV3 class 0x11: Image",
"NV3 class 0x12: Bitmap",
"NV3 INVALID class 0x13",
"NV3 class 0x14: Transfer to Memory",
"NV3 class 0x15: Stretched image from CPU",
"NV3 INVALID class 0x16",
"NV3 class 0x17: Direct3D 5.0 accelerated textured triangle w/zeta buffer",
"NV3 class 0x18: Point with zeta buffer",
"NV3 INVALID class 0x19",
"NV3 INVALID class 0x1A",
"NV3 INVALID class 0x1B",
"NV3 class 0x1C: Image in Memory",
"NV3 INVALID class 0x1D",
"NV3 INVALID class 0x1E",
"NV3 INVALID class 0x1F",
};

View File

@@ -0,0 +1,67 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* NV3: Methods shared across multiple classes
*
*
*
* Authors: Connor Hyde, <mario64crashed@gmail.com> I need a better email address ;^)
*
* Copyright 2024-2025 Connor Hyde
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/dma.h>
#include <86box/mem.h>
#include <86box/pci.h>
#include <86box/rom.h>
#include <86box/video.h>
#include <86box/nv/vid_nv.h>
#include <86box/nv/vid_nv3.h>
void nv3_generic_method(uint32_t param, uint32_t method_id, nv3_ramin_context_t context, nv3_grobj_t grobj)
{
switch (method_id)
{
/* mthdCreate in software(?)*/
case NV3_ROOT_HI_IM_OBJECT_MCOBJECTYFACE:
//nv_log("mthdCreate obj_name=0x%08x\n", param);
nv3_pgraph_interrupt_invalid(NV3_PGRAPH_INTR_1_SOFTWARE_METHOD_PENDING);
break;
// set up the current notification request/object
// and check for double notifiers.
case NV3_SET_NOTIFY:
if (nv3->pgraph.notify_pending)
{
nv_log("Executed method NV3_SET_NOTIFY with nv3->pgraph.notify_pending already set. param=0x%08x, method=0x%04x, grobj=0x%08x 0x%08x 0x%08x 0x%08x\n");
nv_log("IF THIS IS A DEBUG BUILD, YOU SHOULD SEE A CONTEXT BELOW");
nv3_debug_ramin_print_context_info(param, context);
nv3_pgraph_interrupt_invalid(NV3_PGRAPH_INTR_1_DOUBLE_NOTIFY);
// disable
nv3->pgraph.notify_pending = false;
nv3_pgraph_interrupt_invalid(NV3_PGRAPH_INTR_1_DOUBLE_NOTIFY);
/* may need to disable fifo in this state */
return;
}
// set a notify as pending.
nv3->pgraph.notifier = param;
nv3->pgraph.notify_pending = true;
break;
default:
nv_log("Shared Generic Methods: Invalid or Unimplemented method 0x%04x", method_id);
nv3_pgraph_interrupt_invalid(NV3_PGRAPH_INTR_1_SOFTWARE_METHOD_PENDING);
return;
}
}

1567
src/video/nv/nv3/nv3_core.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,213 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* The insane NV3 MMIO arbiter.
* Writes to ALL sections of the GPU based on the write position
* All writes are internally considered to be 32-bit! Be careful...
*
* Also handles interrupt dispatch
*
*
*
* Authors: Connor Hyde, <mario64crashed@gmail.com> I need a better email address ;^)
*
* Copyright 2024-2025 starfrost
*/
// STANDARD NV3 includes
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/pci.h>
#include <86box/rom.h> // DEPENDENT!!!
#include <86box/video.h>
#include <86box/nv/vid_nv.h>
#include <86box/nv/vid_nv3.h>
// Gets a register...
// move this somewhere else when we have more models
nv_register_t* nv_get_register(uint32_t address, nv_register_t* register_list, uint32_t num_regs)
{
for (int32_t reg_num = 0; reg_num < num_regs; reg_num++)
{
if (register_list[reg_num].address == NV_REG_LIST_END)
break; //unimplemented
if (register_list[reg_num].address == address)
return &register_list[reg_num];
}
return NULL;
}
// Arbitrates an MMIO read
uint32_t nv3_mmio_arbitrate_read(uint32_t address)
{
// sanity check
if (!nv3)
return 0x00;
uint32_t ret = 0x00;
// Ensure the addresses are dword aligned.
// I don't know why this is needed because writepriv32 is always to dword align, but it crashes if you don't do this.
if (!(address >= NV3_USER_DAC_PALETTE_START && address <= NV3_USER_DAC_PALETTE_END))
address &= 0xFFFFFC;
// gigantic set of if statements to send the write to the right subsystem
if (address >= NV3_PMC_START && address <= NV3_PMC_END)
ret = nv3_pmc_read(address);
else if (address >= NV3_CIO_START && address <= NV3_CIO_END)
ret = nv3_cio_read(address);
else if (address >= NV3_PBUS_PCI_START && address <= NV3_PBUS_PCI_END)
ret = nv3_pci_read(0x00, address & 0xFF, NULL);
else if (address >= NV3_PBUS_START && address <= NV3_PBUS_END)
ret = nv3_pbus_read(address);
else if (address >= NV3_PFIFO_START && address <= NV3_PFIFO_END)
ret = nv3_pfifo_read(address);
else if (address >= NV3_PFB_START && address <= NV3_PFB_END)
ret = nv3_pfb_read(address);
else if (address >= NV3_PRM_START && address <= NV3_PRM_END)
ret = nv3_prm_read(address);
else if (address >= NV3_PRMIO_START && address <= NV3_PRMIO_END)
ret = nv3_prmio_read(address);
else if (address >= NV3_PTIMER_START && address <= NV3_PTIMER_END)
ret = nv3_ptimer_read(address);
else if (address >= NV3_PFB_START && address <= NV3_PFB_END)
ret = nv3_pfb_read(address);
else if (address >= NV3_PEXTDEV_START && address <= NV3_PEXTDEV_END)
ret = nv3_pextdev_read(address);
else if (address >= NV3_PROM_START && address <= NV3_PROM_END)
ret = nv3_prom_read(address);
else if (address >= NV3_PALT_START && address <= NV3_PALT_END)
ret = nv3_palt_read(address);
else if (address >= NV3_PME_START && address <= NV3_PME_END)
ret = nv3_pme_read(address);
else if (address >= NV3_PGRAPH_START && address <= NV3_PGRAPH_REAL_END) // what we're actually doing here determined by nv3_pgraph_* func
ret = nv3_pgraph_read(address);
else if (address >= NV3_PRMCIO_START && address <= NV3_PRMCIO_END)
ret = nv3_prmcio_read(address);
else if (address >= NV3_PVIDEO_START && address <= NV3_PVIDEO_END)
ret = nv3_pvideo_read(address);
else if ((address >= NV3_PRAMDAC_START && address <= NV3_PRAMDAC_END)
|| (address >= NV3_USER_DAC_PALETTE_START && address <= NV3_USER_DAC_PALETTE_END)) //clut
ret = nv3_pramdac_read(address);
else if (address >= NV3_VRAM_START && address <= NV3_VRAM_END)
ret = nv3_dfb_read32(address & nv3->nvbase.svga.vram_mask, &nv3->nvbase.svga);
else if (address >= NV3_USER_START && address <= NV3_USER_END)
ret = nv3_user_read(address);
else
{
//nvplay stuff
//#ifdef ENABLE_NV_LOG_ULTRA
//warning("MMIO read arbitration failed, INVALID address NOT mapped to any GPU subsystem 0x%08x [returning unmapped pattern]\n", address);
//#else
nv_log("MMIO read arbitration failed, INVALID address NOT mapped to any GPU subsystem 0x%08x [returning unmapped pattern]\n", address);
//#endif
// The real hardware returns a garbage pattern
return 0x00;
}
return ret;
}
void nv3_mmio_arbitrate_write(uint32_t address, uint32_t value)
{
// sanity check
if (!nv3)
return;
// Some of these addresses are Weitek VGA stuff and we need to mask it to this first because the weitek addresses are 8-bit aligned.
address &= 0xFFFFFF;
// Ensure the addresses are dword aligned.
// I don't know why this is needed because writepriv32 is always dword aligned in Nvidia's drivers, but it crashes if you don't do this.
// Exclude the 4bpp/8bpp CLUT for this purpose
if (!(address >= NV3_USER_DAC_PALETTE_START && address <= NV3_USER_DAC_PALETTE_END))
address &= 0xFFFFFC;
// gigantic set of if statements to send the write to the right subsystem
if (address >= NV3_PMC_START && address <= NV3_PMC_END)
nv3_pmc_write(address, value);
else if (address >= NV3_CIO_START && address <= NV3_CIO_END)
nv3_cio_write(address, value);
else if (address >= NV3_PBUS_PCI_START && address <= NV3_PBUS_PCI_END) // PCI mirrored at 0x1800 in MMIO
nv3_pci_write(0x00, address & 0xFF, value, NULL); // priv does not matter
else if (address >= NV3_PBUS_START && address <= NV3_PBUS_END)
nv3_pbus_write(address, value);
else if (address >= NV3_PFIFO_START && address <= NV3_PFIFO_END)
nv3_pfifo_write(address, value);
else if (address >= NV3_PRM_START && address <= NV3_PRM_END)
nv3_prm_write(address, value);
else if (address >= NV3_PRMIO_START && address <= NV3_PRMIO_END)
nv3_prmio_write(address, value);
else if (address >= NV3_PTIMER_START && address <= NV3_PTIMER_END)
nv3_ptimer_write(address, value);
else if (address >= NV3_PFB_START && address <= NV3_PFB_END)
nv3_pfb_write(address, value);
else if (address >= NV3_PEXTDEV_START && address <= NV3_PEXTDEV_END)
nv3_pextdev_write(address, value);
else if (address >= NV3_PROM_START && address <= NV3_PROM_END)
nv3_prom_write(address, value);
else if (address >= NV3_PALT_START && address <= NV3_PALT_END)
nv3_palt_write(address, value);
else if (address >= NV3_PME_START && address <= NV3_PME_END)
nv3_pme_write(address, value);
else if (address >= NV3_PGRAPH_START && address <= NV3_PGRAPH_REAL_END) // what we're actually doing here is determined by the nv3_pgraph_* functions
nv3_pgraph_write(address, value);
else if (address >= NV3_PRMCIO_START && address <= NV3_PRMCIO_END)
nv3_prmcio_write(address, value);
else if (address >= NV3_PVIDEO_START && address <= NV3_PVIDEO_END)
nv3_pvideo_write(address, value);
else if ((address >= NV3_PRAMDAC_START && address <= NV3_PRAMDAC_END)
|| (address >= NV3_USER_DAC_PALETTE_START && address <= NV3_USER_DAC_PALETTE_END)) //clut
nv3_pramdac_write(address, value);
else if (address >= NV3_VRAM_START && address <= NV3_VRAM_END)
nv3_dfb_write32(address, value, &nv3->nvbase.svga);
else if (address >= NV3_USER_START && address <= NV3_USER_END)
nv3_user_write(address, value);
//RAMIN is its own thing
else
{
//nvplay stuff
//#ifdef ENABLE_NV_LOG_ULTRA
//warning("MMIO write arbitration failed, INVALID address NOT mapped to any GPU subsystem 0x%08x [returning 0x00]\n", address);
//#else
nv_log("MMIO write arbitration failed, INVALID address NOT mapped to any GPU subsystem 0x%08x [returning 0x00]\n", address);
//#endif
return;
}
}
// //
// ******* DUMMY FUNCTIONS FOR UNIMPLEMENTED SUBSYSTEMS ******* //
// //
// Read and Write functions for GPU subsystems
// Remove the ones that aren't used here eventually, have all of htem for now
uint32_t nv3_cio_read(uint32_t address) { return 0; };
void nv3_cio_write(uint32_t address, uint32_t value) {};
uint32_t nv3_prm_read(uint32_t address) { return 0; };
void nv3_prm_write(uint32_t address, uint32_t value) {};
uint32_t nv3_prmio_read(uint32_t address) { return 0; };
void nv3_prmio_write(uint32_t address, uint32_t value) {};
uint32_t nv3_palt_read(uint32_t address) { return 0; };
void nv3_palt_write(uint32_t address, uint32_t value) {};
// TODO: PGRAPH class registers
uint32_t nv3_prmcio_read(uint32_t address) { return 0; };
void nv3_prmcio_write(uint32_t address, uint32_t value) {};

View File

@@ -0,0 +1,240 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* Provides NV3 configuration
*
*
* Authors: Connor Hyde, <mario64crashed@gmail.com> I need a better email address ;^)
*
* Copyright 2024-2025 starfrost
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/io.h>
#include <86box/pci.h>
#include <86box/rom.h> // DEPENDENT!!!
#include <86box/video.h>
#include <86box/nv/vid_nv.h>
#include <86box/nv/vid_nv3.h>
const device_config_t nv3_config[] =
{
// VBIOS type configuration
{
.name = "vbios",
.description = "Model",
.type = CONFIG_BIOS,
.default_string = "NV3_VBIOS_ERAZOR_V15403",
.default_int = 0,
.bios =
{
{
.name = "ELSA VICTORY Erazor - Version 1.47.00", .files_no = 1,
.internal_name = "NV3_VBIOS_ERAZOR_V14700",
.files = {NV3_VBIOS_ERAZOR_V14700, ""}
},
{
.name = "ELSA VICTORY Erazor - Version 1.54.03", .files_no = 1,
.internal_name = "NV3_VBIOS_ERAZOR_V15403",
.files = {NV3_VBIOS_ERAZOR_V15403, ""}
},
{
.name = "ELSA VICTORY Erazor - Version 1.55.00", .files_no = 1,
.internal_name = "NV3_VBIOS_ERAZOR_V15500",
.files = {NV3_VBIOS_ERAZOR_V15500, ""}
},
{
.name = "Diamond Viper V330 - Version 1.62-CO", .files_no = 1,
.internal_name = "NV3_VBIOS_DIAMOND_V330_V162",
.files = {NV3_VBIOS_DIAMOND_V330_V162, ""},
},
{
.name = "ASUS AGP/3DP-V3000 - Version 1.51B", .files_no = 1,
.internal_name = "NV3_VBIOS_ASUS_V3000_V151",
.files = {NV3_VBIOS_ASUS_V3000_V151, ""},
},
{
.name = "STB Velocity 128 - Version 1.60 [BUGGY]", .files_no = 1,
.internal_name = "NV3_VBIOS_STB_V128_V160",
.files = {NV3_VBIOS_STB_V128_V160, ""},
},
{
.name = "STB Velocity 128 - Version 1.82", .files_no = 1,
.internal_name = "NV3_VBIOS_STB_V128_V182",
.files = {NV3_VBIOS_STB_V128_V182, ""},
},
}
},
// Memory configuration
{
.name = "vram_size",
.description = "VRAM Size",
.type = CONFIG_SELECTION,
.default_int = NV3_VRAM_SIZE_4MB,
.selection =
{
// I thought this was never released, but it seems that at least one was released:
// The card was called the "NEC G7AGK"
{
.description = "2 MB",
.value = NV3_VRAM_SIZE_2MB,
},
{
.description = "4 MB",
.value = NV3_VRAM_SIZE_4MB,
},
}
},
{
.name = "chip_revision",
.description = "Chip Revision",
.type = CONFIG_SELECTION,
.default_int = NV3_PCI_CFG_REVISION_B00,
.selection =
{
{
.description = "RIVA 128 Prototype (Revision A; January 1997)",
.value = NV3_PCI_CFG_REVISION_A00,
},
{
.description = "RIVA 128 (Revision B)",
.value = NV3_PCI_CFG_REVISION_B00,
},
}
},
// Multithreading configuration
{
.name = "pgraph_threads",
#ifndef RELEASE_BUILD
.description = "PFIFO/PGRAPH - Number of threads to split large object method execution into",
#else
.description = "Render threads",
#endif
.type = CONFIG_SELECTION,
.default_int = 1, // todo: change later
.selection =
{
{
.description = "1 thread (Only use if issues appear with more threads)",
.value = 1,
},
{
.description = "2 threads",
.value = 2,
},
{
.description = "4 threads",
.value = 4,
},
{
.description = "8 threads",
.value = 8,
},
},
},
#ifndef RELEASE_BUILD
{
.name = "nv_debug_fulllog",
.description = "Disable Cyclical Lines Detection for nv_log (Use for getting full context at cost of VERY large log files)",
.type = CONFIG_BINARY,
.default_int = 0,
},
#endif
{
.type = CONFIG_END
}
};
const device_config_t nv3t_config[] =
{
// VBIOS type configuration
{
.name = "vbios",
.description = "Model",
.type = CONFIG_BIOS,
.default_string = "NV3T_VBIOS_DIAMOND_V330_V182B",
.default_int = 0,
.bios =
{
{
.name = "Diamond Multimedia Viper V330 8M BIOS - Version 1.82B", .files_no = 1,
.internal_name = "NV3T_VBIOS_DIAMOND_V330_V182B",
.files = {NV3T_VBIOS_DIAMOND_V330_V182B, ""},
},
{
.name = "ASUS AGP-V3000 ZXTV BIOS - V1.70D.03", .files_no = 1,
.internal_name = "NV3T_VBIOS_ASUS_V170",
.files = {NV3T_VBIOS_ASUS_V170, ""},
},
{
.name = "NVidia Reference BIOS - V1.71B-N", .files_no = 1,
.internal_name = "NV3T_VBIOS_REFERENCE_CEK_V171",
.files = {NV3T_VBIOS_REFERENCE_CEK_V171, ""},
},
{
.name = "NVidia Reference BIOS - V1.72B", .files_no = 1,
.internal_name = "NV3T_VBIOS_REFERENCE_CEK_V172",
.files = {NV3T_VBIOS_REFERENCE_CEK_V172, ""},
},
}
},
// Multithreading configuration
{
.name = "pgraph_threads",
#ifndef RELEASE_BUILD
.description = "PFIFO/PGRAPH - Number of threads to split large object method execution into",
#else
.description = "Render threads",
#endif
.type = CONFIG_SELECTION,
.default_int = 1, // todo: change later
.selection =
{
{
.description = "1 thread (Only use if issues appear with more threads)",
.value = 1,
},
{
.description = "2 threads",
.value = 2,
},
{
.description = "4 threads",
.value = 4,
},
{
.description = "8 threads",
.value = 8,
},
},
},
#ifndef RELEASE_BUILD
{
.name = "nv_debug_fulllog",
.description = "Disable Cyclical Lines Detection for nv_log (Use for getting full context at cost of VERY large log files)",
.type = CONFIG_BINARY,
.default_int = 0,
},
#endif
{
.type = CONFIG_END
}
};

View File

@@ -0,0 +1,229 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* NV3 Core rendering code (Software version)
*
*
*
* Authors: Connor Hyde, <mario64crashed@gmail.com> I need a better email address ;^)
*
* Copyright 2024-2025 Connor Hyde
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/pci.h>
#include <86box/plat.h>
#include <86box/rom.h>
#include <86box/video.h>
#include <86box/nv/vid_nv.h>
#include <86box/nv/vid_nv3.h>
/* Check the line bounds */
void nv3_class_011_check_line_bounds(void)
{
uint32_t relative_x = nv3->pgraph.image_current_position.x - nv3->pgraph.image.point.x;
/* In the case of class 0x11 there is no requirement to check for relative_y because we have exceeded the size of the image */
if (relative_x >= nv3->pgraph.image.size_in.x)
{
nv3->pgraph.image_current_position.y++;
nv3->pgraph.image_current_position.x = nv3->pgraph.image.point.x;
}
}
/* Renders an image from cpu */
void nv3_render_blit_image(uint32_t color, nv3_grobj_t grobj)
{
/* todo: a lot of stuff */
uint32_t pixel0 = 0, pixel1 = 0, pixel2 = 0, pixel3 = 0;
/* Some extra data is sent as padding, we need to clip it off using size_out */
uint16_t clip_x = nv3->pgraph.image.point.x + nv3->pgraph.image.size.x;
/* we need to unpack them - IF THIS IS USED SOMEWHERE ELSE, DO SOMETHING ELSE WITH IT */
/* the reverse order is due to the endianness */
switch (nv3->nvbase.svga.bpp)
{
// 4 pixels packed into one color in 8bpp
case 8:
//pixel3
pixel3 = color & 0xFF;
if (nv3->pgraph.image_current_position.x < clip_x) nv3_render_write_pixel(nv3->pgraph.image_current_position, pixel3, grobj);
nv3->pgraph.image_current_position.x++;
nv3_class_011_check_line_bounds();
pixel2 = (color >> 8) & 0xFF;
if (nv3->pgraph.image_current_position.x < clip_x) nv3_render_write_pixel(nv3->pgraph.image_current_position, pixel2, grobj);
nv3->pgraph.image_current_position.x++;
nv3_class_011_check_line_bounds();
pixel1 = (color >> 16) & 0xFF;
if (nv3->pgraph.image_current_position.x < clip_x) nv3_render_write_pixel(nv3->pgraph.image_current_position, pixel1, grobj);
nv3->pgraph.image_current_position.x++;
nv3_class_011_check_line_bounds();
pixel0 = (color >> 24) & 0xFF;
if (nv3->pgraph.image_current_position.x < clip_x) nv3_render_write_pixel(nv3->pgraph.image_current_position, pixel0, grobj);
nv3->pgraph.image_current_position.x++;
nv3_class_011_check_line_bounds();
break;
// 2 pixels packed into one color in 15/16bpp
case 15:
case 16:
pixel1 = (color) & 0xFFFF;
if (nv3->pgraph.image_current_position.x < (clip_x)) nv3_render_write_pixel(nv3->pgraph.image_current_position, pixel1, grobj);
nv3->pgraph.image_current_position.x++;
nv3_class_011_check_line_bounds();
pixel0 = (color >> 16) & 0xFFFF;
if (nv3->pgraph.image_current_position.x < (clip_x)) nv3_render_write_pixel(nv3->pgraph.image_current_position, pixel0, grobj);
nv3->pgraph.image_current_position.x++;
nv3_class_011_check_line_bounds();
break;
// just one pixel in 32bpp
case 32:
if (nv3->pgraph.image_current_position.x < clip_x) nv3_render_write_pixel(nv3->pgraph.image_current_position, color, grobj);
nv3->pgraph.image_current_position.x++;
nv3_class_011_check_line_bounds();
break;
}
}
#define NV3_MAX_HORIZONTAL_SIZE 1920
#define NV3_MAX_VERTICAL_SIZE 1200
/* 1920 for margin. Holds a buffer of the old screen we want to hold so we don't overwrite things we already overwtote
We only need to clear it once per blit, because the blits are always the same size, and then only for the size of our new blit
Extremely not crazy about this...Surely a better way to do it without buffering the ENTIRE SCREEN. I only update the parts that are needed, but still...
This is LUDICROUSLY INEFFICIENT (2*O(n^2)) and COMPLETELY TERRIBLE code, but it's currently 2:48am so I can't think of a better approach...
*/
uint32_t nv3_s2sb_line_buffer[NV3_MAX_HORIZONTAL_SIZE*NV3_MAX_VERTICAL_SIZE] = {0};
void nv3_render_blit_screen2screen_for_buffer(nv3_grobj_t grobj, uint32_t dst_buffer)
{
if (nv3->pgraph.blit.size.x < NV3_MAX_HORIZONTAL_SIZE
&& nv3->pgraph.blit.size.y < NV3_MAX_VERTICAL_SIZE)
memset(&nv3_s2sb_line_buffer, 0x00, (sizeof(uint32_t) * nv3->pgraph.blit.size.y) * (sizeof(uint32_t) * nv3->pgraph.blit.size.x));
/* First calculate our source and destination buffer */
uint32_t src_buffer = (grobj.grobj_0 >> NV3_PGRAPH_CTX_SWITCH_SRC_BUFFER) & 0x03;
nv3_coord_16_t in_position = nv3->pgraph.blit.point_in;
nv3_coord_16_t out_position = nv3->pgraph.blit.point_out;
/* Coordinates for copying an entire line at a time */
uint32_t buf_position = 0, vram_position = 0, size_x = nv3->pgraph.blit.size.x;
/*
Read the old pixel into the line buffer
Assumption: All data is sent in an unpacked format. In the case of an NVIDIA GPU this means that all data is sent 32 bits at a time regardless of if
the actual source data is 32 bits in size or not. For pixel data, the upper bits are left as 0 in 8bpp/16bpp mode. For 86box purposes, the data is written
8/16 bits at a time.
TODO: CHECK FOR PACKED FORMAT!!!!!
*/
if (nv3->nvbase.svga.bpp == 15
|| nv3->nvbase.svga.bpp == 16)
size_x <<= 1;
else if (nv3->nvbase.svga.bpp == 32)
size_x <<= 2;
for (int32_t y = 0; y < nv3->pgraph.blit.size.y; y++)
{
buf_position = (nv3->pgraph.blit.size.x * y);
/* shouldn't matter in non-wtf mode */
vram_position = nv3_render_get_vram_address_for_buffer(in_position, src_buffer);
memcpy(&nv3_s2sb_line_buffer[buf_position], &nv3->nvbase.svga.vram[vram_position], size_x);
in_position.y++;
/* 32bit buffer */
}
/* simply write it all back to vram */
for (int32_t y = 0; y < nv3->pgraph.blit.size.y; y++)
{
buf_position = (nv3->pgraph.blit.size.x * y);
vram_position = nv3_render_get_vram_address_for_buffer(out_position, dst_buffer);
memcpy(&nv3->nvbase.svga.vram[vram_position], &nv3_s2sb_line_buffer[buf_position], size_x);
out_position.y++;
}
/*
//32bit only as a test
uint32_t* vram_32 = (uint32_t*)nv3->nvbase.svga.vram;
if (nv3->pgraph.boffset[src_buffer] != nv3->pgraph.boffset[dst_buffer])
{
// stretch out the position to the new one
nv3_coord_16_t current_pos_in;
nv3_coord_16_t current_pos_out;
current_pos_in.x = nv3->pgraph.blit.point_in.x;
current_pos_in.y = nv3->pgraph.blit.point_in.y;
current_pos_out.x = nv3->pgraph.blit.point_out.x;
current_pos_out.y = nv3->pgraph.blit.point_out.y;
for (uint32_t y = 0; y < nv3->pgraph.blit.size.y; y++)
{
current_pos_in.y = nv3->pgraph.blit.point_in.y + y;
current_pos_out.y = nv3->pgraph.blit.point_out.y + y;
for (uint32_t x = 0; x < nv3->pgraph.blit.size.x; x++)
{
current_pos_in.x = nv3->pgraph.blit.point_in.x + x;
current_pos_out.x = nv3->pgraph.blit.point_out.x + x;
uint32_t index = nv3_render_get_vram_address_for_buffer(current_pos_in, dst_buffer) >> 2;
uint32_t index_dst = nv3_render_get_vram_address_for_buffer(current_pos_out, src_buffer) >> 2;
vram_32[index_dst] = vram_32[index];
//nv3_render_write_pixel(current_pos, vram_32[index], grobj);
}
current_pos_in.x = nv3->pgraph.blit.point_in.x;
current_pos_out.x = nv3->pgraph.blit.point_out.x;
}
}
*/
}
void nv3_render_blit_screen2screen(nv3_grobj_t grobj)
{
uint32_t dst_buffer = (nv3_pgraph_destination_buffer)grobj.grobj_0; // 5 = just use the source buffer
if (dst_buffer & pgraph_dest_buffer0)
nv3_render_blit_screen2screen_for_buffer(grobj, 0);
if (dst_buffer & pgraph_dest_buffer1)
nv3_render_blit_screen2screen_for_buffer(grobj, 1);
if (dst_buffer & pgraph_dest_buffer2)
nv3_render_blit_screen2screen_for_buffer(grobj, 2);
if (dst_buffer & pgraph_dest_buffer3)
nv3_render_blit_screen2screen_for_buffer(grobj, 3);
}

View File

@@ -0,0 +1,869 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* NV3 Core rendering code (Software version)
*
*
*
* Authors: Connor Hyde, <mario64crashed@gmail.com> I need a better email address ;^)
*
* Copyright 2024-2025 Connor Hyde
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/pci.h>
#include <86box/plat.h>
#include <86box/rom.h>
#include <86box/video.h>
#include <86box/nv/vid_nv.h>
#include <86box/nv/vid_nv3.h>
#include <86box/utils/video_stdlib.h>
/* Functions only used in this translation unit */
void nv3_render_8bpp(uint32_t vram_start, nv3_coord_16_t screen_size);
void nv3_render_15bpp(uint32_t vram_start, nv3_coord_16_t screen_size);
void nv3_render_16bpp(uint32_t vram_start, nv3_coord_16_t screen_size);
void nv3_render_32bpp(uint32_t vram_start, nv3_coord_16_t screen_size);
/* Expand a colour.
NOTE: THE GPU INTERNALLY OPERATES ON RGB10!!!!!!!!!!!
*/
nv3_color_expanded_t nv3_render_expand_color(uint32_t color, nv3_grobj_t grobj)
{
// grobj0 = seems to share the format of PGRAPH_CONTEXT_SWITCH register.
uint8_t format = (grobj.grobj_0 & 0x07);
bool alpha_enabled = (grobj.grobj_0 >> NV3_PGRAPH_CTX_SWITCH_ALPHA) & 0x01;
nv3_color_expanded_t color_final;
// set the pixel format
color_final.pixel_format = format;
nv_log_verbose_only("Expanding Colour 0x%08x using pgraph_pixel_format 0x%x alpha enabled=%d\n", color, format, alpha_enabled);
// default to fully opaque in case alpha is disabled
color_final.a = 0xFF;
switch (format)
{
// ALL OF THESE TYPES ARE 32 BITS IN SIZE
// 555
case nv3_pgraph_pixel_format_r5g5b5:
// "stretch out" the colour
color_final.a = (color >> 15) & 0x01; // will be ignored if alpha_enabled isn't used
color_final.r = ((color >> 10) & 0x1F) << 5;
color_final.g = ((color >> 5) & 0x1F) << 5;
color_final.b = (color & 0x1F) << 5;
break;
// 888 (standard colour + 8-bit alpha)
case nv3_pgraph_pixel_format_r8g8b8:
if (alpha_enabled)
color_final.a = ((color >> 24) & 0xFF) * 4;
color_final.r = ((color >> 16) & 0xFF) * 4;
color_final.g = ((color >> 8) & 0xFF) * 4;
color_final.b = (color & 0xFF) * 4;
break;
case nv3_pgraph_pixel_format_r10g10b10:
color_final.a = (color >> 31) & 0x01;
color_final.r = (color >> 30) & 0x3FF;
color_final.g = (color >> 20) & 0x1FF;
color_final.b = (color >> 10);
break;
case nv3_pgraph_pixel_format_y8:
/* Indexed mode */
color_final.a = (color >> 8) & 0xFF;
// yuv
color_final.r = color_final.g = color_final.b = (color & 0xFF) * 4; // convert to rgb10
break;
case nv3_pgraph_pixel_format_y16:
color_final.a = (color >> 16) & 0xFFFF;
// yuv
color_final.r = color_final.g = color_final.b = (color & 0xFFFF) * 4; // convert to rgb10
break;
case nv3_pgraph_pixel_format_y420:
warning("nv3_render_expand_color: YUV420 not implemented\n");
break;
default:
warning("nv3_render_expand_color unknown format %d", format);
break;
}
// i8 is a union under i16
color_final.i16 = (color & 0xFFFF);
return color_final;
}
/* Used for chroma test */
uint32_t nv3_render_downconvert_color(nv3_grobj_t grobj, nv3_color_expanded_t color)
{
uint8_t format = (grobj.grobj_0 & 0x07);
bool alpha_enabled = (grobj.grobj_0 >> NV3_PGRAPH_CTX_SWITCH_ALPHA) & 0x01;
nv_log_verbose_only("Downconverting Colour 0x%08x using pgraph_pixel_format 0x%x alpha enabled=%d\n", color, format, alpha_enabled);
uint32_t packed_color = 0x00;
switch (format)
{
case nv3_pgraph_pixel_format_r5g5b5:
packed_color = (color.r >> 5) << 10 |
(color.g >> 5) << 5 |
(color.b >> 5);
break;
case nv3_pgraph_pixel_format_r8g8b8:
packed_color = (color.a) << 24 | // is this a thing?
(color.r >> 2) << 16 |
(color.g >> 2) << 8 |
color.b;
break;
case nv3_pgraph_pixel_format_r10g10b10:
/* sometimes alpha isn't used but we should incorporate it anyway */
if (color.a > 0x00) packed_color |= (1 << 31);
packed_color |= (color.r << 30);
packed_color |= (color.g << 20);
packed_color |= (color.b << 10);
break;
case nv3_pgraph_pixel_format_y8: /* i think this is just indexed mode. since r=g=b we can just take the indexed from the r */
packed_color = nv3_render_get_palette_index((color.r >> 2) & 0xFF);
break;
case nv3_pgraph_pixel_format_y16:
warning("nv3_render_downconvert_color: Y16 not implemented");
break;
case nv3_pgraph_pixel_format_y420:
warning("nv3_render_downconvert_color: YUV420 not implemented\n");
break;
default:
warning("nv3_render_downconvert_color unknown format %d", format);
break;
}
return packed_color;
}
/* Runs the chroma key/color key test */
bool nv3_render_chroma_test(uint32_t color, nv3_grobj_t grobj)
{
bool chroma_enabled = ((grobj.grobj_0 >> NV3_PGRAPH_CTX_SWITCH_CHROMA_KEY) & 0x01);
if (!chroma_enabled)
return true;
bool alpha = ((nv3->pgraph.chroma_key >> 31) & 0x01);
if (!alpha)
return true;
/* this is dumb but i'm lazy, if it kills perf, fix it later - we need to do some format shuffling */
nv3_grobj_t grobj_fake = {0};
grobj_fake.grobj_0 = 0x02; /* we don't care about any other bits */
nv3_color_expanded_t chroma_expanded = nv3_render_expand_color(nv3->pgraph.chroma_key, grobj_fake);
uint32_t chroma_downconverted = nv3_render_downconvert_color(grobj, chroma_expanded);
return !(chroma_downconverted == color);
}
/* Convert expanded colour format to chroma key format */
uint32_t nv3_render_to_chroma(nv3_color_expanded_t expanded)
{
// convert the alpha to 1 bit. then return packed rgb10
return !!expanded.a | (expanded.r << 30) | (expanded.g << 20) | (expanded.b << 10);
}
/* Get a colour for a palette index. (The colours are 24 bit RGB888 with a 0xFF alpha added for some purposes.) */
uint32_t nv3_render_get_palette_index(uint8_t index)
{
uint32_t red_index = index * 3;
uint32_t green_index = red_index + 1;
uint32_t blue_index = red_index + 2;
uint8_t red_colour = nv3->pramdac.palette[red_index];
uint8_t green_colour = nv3->pramdac.palette[green_index];
uint8_t blue_colour = nv3->pramdac.palette[blue_index];
/* Alpha is always 0xFF */
return (0xFF << 24) | ((red_colour) << 16) | ((green_colour) << 8) | blue_colour;
}
/* Convert a rgb10 colour to a pattern colour */
void nv3_render_set_pattern_color(nv3_color_expanded_t pattern_colour, bool use_color1)
{
/* select the right pattern colour, _rgb is already in RGB10 format, so we don't need to do any conversion */
if (!use_color1)
{
nv3->pgraph.pattern_color_0_alpha = (pattern_colour.a) & 0xFF;
nv3->pgraph.pattern_color_0_rgb.r = pattern_colour.r;
nv3->pgraph.pattern_color_0_rgb.g = pattern_colour.g;
nv3->pgraph.pattern_color_0_rgb.b = pattern_colour.b;
}
else
{
nv3->pgraph.pattern_color_1_alpha = (pattern_colour.a) & 0xFF;
nv3->pgraph.pattern_color_1_rgb.r = pattern_colour.r;
nv3->pgraph.pattern_color_1_rgb.g = pattern_colour.g;
nv3->pgraph.pattern_color_1_rgb.b = pattern_colour.b;
}
}
/* Combine the current buffer with the pitch to get the address in the framebuffer to draw from for a given position. */
uint32_t nv3_render_get_vram_address(nv3_coord_16_t position, nv3_grobj_t grobj)
{
uint32_t vram_x = position.x;
uint32_t vram_y = position.y;
uint32_t current_buffer = (grobj.grobj_0 >> NV3_PGRAPH_CTX_SWITCH_SRC_BUFFER) & 0x03;
uint32_t framebuffer_bpp = nv3->nvbase.svga.bpp;
// we have to multiply the x position by the number of bytes per pixel
switch (framebuffer_bpp)
{
case 8:
break;
case 15:
case 16:
vram_x = position.x << 1;
break;
case 32:
vram_x = position.x << 2;
break;
}
uint32_t pixel_addr_vram = vram_x + (nv3->pgraph.bpitch[current_buffer] * vram_y) + nv3->pgraph.boffset[current_buffer];
pixel_addr_vram &= nv3->nvbase.svga.vram_mask;
return pixel_addr_vram;
}
/* Combine the current buffer with the pitch to get the address in the video ram for a specific position relative to a specific framebuffer */
uint32_t nv3_render_get_vram_address_for_buffer(nv3_coord_16_t position, uint32_t buffer)
{
uint32_t vram_x = position.x;
uint32_t vram_y = position.y;
uint32_t framebuffer_bpp = nv3->nvbase.svga.bpp;
// we have to multiply the x position by the number of bytes per pixel
switch (framebuffer_bpp)
{
case 8:
break;
case 15:
case 16:
vram_x = position.x << 1;
break;
case 32:
vram_x = position.x << 2;
break;
}
uint32_t pixel_addr_vram = vram_x + (nv3->pgraph.bpitch[buffer] * vram_y) + nv3->pgraph.boffset[buffer];
pixel_addr_vram &= nv3->nvbase.svga.vram_mask;
return pixel_addr_vram;
}
/* Convert a dumb framebuffer address to a position. No buffer setup or anything, but just start at 0,0 for address 0. */
nv3_coord_16_t nv3_render_get_dfb_position(uint32_t vram_address)
{
nv3_coord_16_t pos = {0};
uint32_t pitch = nv3->nvbase.svga.hdisp;
if (nv3->nvbase.svga.bpp == 15
|| nv3->nvbase.svga.bpp == 16)
pitch <<= 1;
else if (nv3->nvbase.svga.bpp == 32)
pitch <<= 2;
pos.y = (vram_address / pitch);
pos.x = (vram_address % pitch);
/* Fixup our x position */
if (nv3->nvbase.svga.bpp == 15
|| nv3->nvbase.svga.bpp == 16)
pos.x >>= 1;
else if (nv3->nvbase.svga.bpp == 32)
pos.x >>= 2;
/* there is some strange behaviour where it writes long past the end of the fb */
if (pos.y >= nv3->nvbase.svga.monitor->target_buffer->h) pos.y = nv3->nvbase.svga.monitor->target_buffer->h - 1;
return pos;
}
/* Read an 8bpp pixel from the framebuffer. */
uint8_t nv3_render_read_pixel_8(nv3_coord_16_t position, nv3_grobj_t grobj)
{
// hope you call it with the right bit
uint32_t vram_address = nv3_render_get_vram_address(position, grobj);
return nv3->nvbase.svga.vram[vram_address];
}
/* Read an 16bpp pixel from the framebuffer. */
uint16_t nv3_render_read_pixel_16(nv3_coord_16_t position, nv3_grobj_t grobj)
{
// hope you call it with the right bit
uint32_t vram_address = nv3_render_get_vram_address(position, grobj);
uint16_t* vram_16 = (uint16_t*)(nv3->nvbase.svga.vram);
vram_address >>= 1; //convert to 16bit pointer
return vram_16[vram_address];
}
/* Read an 16bpp pixel from the framebuffer. */
uint32_t nv3_render_read_pixel_32(nv3_coord_16_t position, nv3_grobj_t grobj)
{
// hope you call it with the right bit
uint32_t vram_address = nv3_render_get_vram_address(position, grobj);
uint32_t* vram_32 = (uint32_t*)(nv3->nvbase.svga.vram);
vram_address >>= 2; //convert to 32bit pointer
return vram_32[vram_address];
}
void nv3_render_write_pixel_to_buffer(nv3_coord_16_t position, uint32_t color, nv3_grobj_t grobj, uint32_t buffer)
{
bool alpha_enabled = (grobj.grobj_0 >> NV3_PGRAPH_CTX_SWITCH_ALPHA) & 0x01;
int32_t clip_end_x = nv3->pgraph.clip_start.x + nv3->pgraph.clip_size.x;
int32_t clip_end_y = nv3->pgraph.clip_start.y + nv3->pgraph.clip_size.y;
/* First, check our current buffer. */
/* Then do the clip. */
if (position.x < nv3->pgraph.clip_start.x
|| position.y < nv3->pgraph.clip_start.y
|| position.x > clip_end_x
|| position.y > clip_end_y)
{
// DO NOT DRAW THE PIXEL
return;
}
/* TODO: Plane Mask...*/
if (!nv3_render_chroma_test(color, grobj))
return;
uint32_t pixel_addr_vram = nv3_render_get_vram_address_for_buffer(position, buffer);
uint32_t rop_src = 0, rop_dst = 0, rop_pattern = 0;
uint8_t bit = 0x00;
/* Get our pattern data, may move to another function */
switch (nv3->pgraph.pattern.shape)
{
/* This logic is from NV1 envytoos docs, but seems to be same on NV3*/
case NV3_PATTERN_SHAPE_8X8:
bit = (position.x & 7) | (position.y & 7) << 3;
break;
case NV3_PATTERN_SHAPE_1X64:
bit = (position.x & 0x3f);
break;
case NV3_PATTERN_SHAPE_64X1:
bit = (position.y & 0x3f);
break;
}
// pull out the actual bit and see which colour we need to use
bool use_color1 = (nv3->pgraph.pattern_bitmap >> bit) & 0x01;
if (!use_color1)
{
if (!nv3->pgraph.pattern_color_0_alpha)
return;
/* This is stupid */
rop_pattern = nv3_render_downconvert_color(grobj, nv3->pgraph.pattern_color_0_rgb);
}
else
{
if (!nv3->pgraph.pattern_color_1_alpha)
return;
rop_pattern = nv3_render_downconvert_color(grobj, nv3->pgraph.pattern_color_1_rgb);
}
/* Go to vram and do the final ROP for a basic bitblit.
It seems we can skip the downconversion step *for now*, since (framebuffer bits per pixel) == (object bits per pixel)
I'm not sure how games will react. But it depends on how the D3D drivers operate, we may need ro convert texture formats to the current bpp internally.
We use the pixel format of the destination buffer to achieve this (thanks frostbite2000)
*/
// translate the patch config to GDI rop
uint32_t final_rop = nv3_render_translate_nvrop(grobj, nv3->pgraph.rop);
uint32_t destination_format = (nv3->pgraph.bpixel[buffer]) & 0x03;
switch (destination_format)
{
case bpixel_fmt_8bit:
rop_src = color & 0xFF;
rop_dst = nv3->nvbase.svga.vram[pixel_addr_vram];
nv3->nvbase.svga.vram[pixel_addr_vram] = video_rop_gdi_ternary(final_rop, rop_src, rop_dst, rop_pattern) & 0xFF;
nv3->nvbase.svga.changedvram[pixel_addr_vram >> 12] = changeframecount;
break;
case bpixel_fmt_16bit:
{
uint16_t* vram_16 = (uint16_t*)(nv3->nvbase.svga.vram);
pixel_addr_vram >>= 1;
// mask to 16bit
rop_src = color & 0xFFFF;
/* if alpha is turned on and we aren't in 565 mode, reject transparent pixels */
bool is_16bpp = (nv3->pramdac.general_control >> NV3_PRAMDAC_GENERAL_CONTROL_565_MODE) & 0x01;
// a1r5g5b5
if (!is_16bpp)
{
if (alpha_enabled &&
!(color & 0x8000))
return;
}
// convert to 15bpp or 16bpp based on if we are in 16bpp mode
rop_dst = vram_16[pixel_addr_vram];
vram_16[pixel_addr_vram] = video_rop_gdi_ternary(final_rop, rop_src, rop_dst, rop_pattern) & 0xFFFF;
nv3->nvbase.svga.changedvram[pixel_addr_vram >> 11] = changeframecount;
break;
}
case bpixel_fmt_32bit:
{
uint32_t* vram_32 = (uint32_t*)(nv3->nvbase.svga.vram);
pixel_addr_vram >>= 2;
rop_src = color;
rop_dst = vram_32[pixel_addr_vram];
vram_32[pixel_addr_vram] = video_rop_gdi_ternary(final_rop, rop_src, rop_dst, rop_pattern);
nv3->nvbase.svga.changedvram[pixel_addr_vram >> 10] = changeframecount;
break;
}
}
}
/* Plots a pixel. */
void nv3_render_write_pixel(nv3_coord_16_t position, uint32_t color, nv3_grobj_t grobj)
{
// PFB_0 is always set to hardcoded "NO_TILING" value of 0x1114.
// It seems, you are meant to use the CRTC
nv3_pgraph_destination_buffer dst_buffer = (nv3_pgraph_destination_buffer)grobj.grobj_0;
if (dst_buffer & (pgraph_dest_buffer0))
nv3_render_write_pixel_to_buffer(position, color, grobj, 0);
if (dst_buffer & (pgraph_dest_buffer1))
nv3_render_write_pixel_to_buffer(position, color, grobj, 1);
if (dst_buffer & (pgraph_dest_buffer2))
nv3_render_write_pixel_to_buffer(position, color, grobj, 2);
if (dst_buffer & (pgraph_dest_buffer3))
nv3_render_write_pixel_to_buffer(position, color, grobj, 3);
}
/* Ensure the correct monitor size */
void nv3_render_ensure_screen_size(void)
{
/* First check if hdisp == xsize and dispend == ysize. */
bool changed = false;
if (nv3->nvbase.svga.hdisp != nv3->nvbase.svga.monitor->mon_xsize)
{
changed = true;
nv3->nvbase.svga.monitor->mon_xsize = nv3->nvbase.svga.hdisp;
}
if (nv3->nvbase.svga.dispend != nv3->nvbase.svga.monitor->mon_ysize)
{
changed = true;
nv3->nvbase.svga.monitor->mon_ysize = nv3->nvbase.svga.dispend;
}
/*
if either changed:
-> set resolution
-> set refresh rate - this is just a rough estimation right now. we need it as we only blit what changes
*/
if (changed)
{
nv3->nvbase.refresh_time = 1 / (nv3->nvbase.pixel_clock_frequency / (double)ysize / (double)xsize); // rivatimers count in microseconds
set_screen_size(xsize, ysize);
}
}
/* Blit to the monitor from DFB, 8bpp */
void nv3_render_current_bpp_dfb_8(uint32_t address)
{
/* Broken as fuck early vbios does this. Wtf? */
if (!nv3->nvbase.svga.hdisp)
return;
nv3_coord_16_t size = {0};
size.x = size.y = 1;
nv3_coord_16_t pos = nv3_render_get_dfb_position(address);
uint32_t* p = &nv3->nvbase.svga.monitor->target_buffer->line[pos.y][pos.x];
uint32_t data = *(uint32_t*)&(nv3->nvbase.svga.vram[address]);
*p = nv3_render_get_palette_index(data & 0xFF);
}
/* Blit to the monitor from DFB, 15/16bpp */
void nv3_render_current_bpp_dfb_16(uint32_t address)
{
/* Broken as fuck early vbios does this. Wtf? */
if (!nv3->nvbase.svga.hdisp)
return;
nv3_coord_16_t size = {0};
size.x = size.y = 1;
nv3_coord_16_t pos = nv3_render_get_dfb_position(address);
uint32_t* p = &nv3->nvbase.svga.monitor->target_buffer->line[pos.y][pos.x];
uint32_t data = *(uint32_t*)&(nv3->nvbase.svga.vram[address]);
if ((nv3->pramdac.general_control >> NV3_PRAMDAC_GENERAL_CONTROL_565_MODE) & 0x01)
/* should just "tip over" to the next line */
*p = nv3->nvbase.svga.conv_16to32(&nv3->nvbase.svga, data & 0xFFFF, 16);
else
/* should just "tip over" to the next line */
*p = nv3->nvbase.svga.conv_16to32(&nv3->nvbase.svga, data & 0xFFFF, 15);
/*does 8bpp packed into 16 occur/ i would be surprised*/
}
/* Blit to the monitor from DFB, 32bpp */
void nv3_render_current_bpp_dfb_32(uint32_t address)
{
/* Broken as fuck early vbios does this. Wtf? */
if (!nv3->nvbase.svga.hdisp)
return;
nv3_coord_16_t size = {0};
size.x = size.y = 1;
nv3_coord_16_t pos = nv3_render_get_dfb_position(address);
uint32_t data = *(uint32_t*)&(nv3->nvbase.svga.vram[address]);
uint32_t* p = &nv3->nvbase.svga.monitor->target_buffer->line[pos.y][pos.x];
if (nv3->nvbase.svga.bpp == 32)
{
*p = data;
}
/* Packed format */
else if (nv3->nvbase.svga.bpp == 15
|| nv3->nvbase.svga.bpp == 16)
{
*p = nv3->nvbase.svga.conv_16to32(&nv3->nvbase.svga, data & 0xFFFF, nv3->nvbase.svga.bpp);
*p++;
*p = nv3->nvbase.svga.conv_16to32(&nv3->nvbase.svga, (data >> 16) & 0xFFFF, nv3->nvbase.svga.bpp);
}
}
/* Blit to the monitor from GPU, current bpp */
void nv3_render_current_bpp()
{
/* Figure out the Display Buffer Address from the CRTC */
uint32_t dba = ((nv3->nvbase.svga.crtc[NV3_CRTC_REGISTER_RPC0] & 0x1F) << 16)
+ (nv3->nvbase.svga.crtc[NV3_CRTC_REGISTER_STARTADDR_HIGH] << 8)
+ nv3->nvbase.svga.crtc[NV3_CRTC_REGISTER_STARTADDR_LOW];
if (nv3->nvbase.debug_dba_enabled
&& nv3->nvbase.debug_dba > 0)
dba = nv3->nvbase.debug_dba;
nv3_coord_16_t screen_size = {0};
screen_size.x = nv3->nvbase.svga.hdisp;
screen_size.y = nv3->nvbase.svga.dispend;
/* Ensure that we are
in the correct mode. Modified SVGA core code */
nv3_render_ensure_screen_size();
/* Don't try and draw stuff that is past the buffer, but, leave it in Video RAM, so it can be used for s2sb's etc */
switch (nv3->nvbase.svga.bpp)
{
case 4:
/* Uh we should never be here because we're in the SVGA mode(?) */
fatal("NV3 - 4bpp not implemented (not even sure if it's SVGA only)");
break;
case 8:
nv3_render_8bpp(dba, screen_size);
break;
case 15:
nv3_render_15bpp(dba, screen_size);
break;
case 16:
nv3_render_16bpp(dba, screen_size);
break;
case 32:
nv3_render_32bpp(dba, screen_size);
break;
}
}
/*
Blit a certain region from the (destination buffer base + (position in vram)) to the 86Box monitor, indexed 8 bits per pixel format
*/
void nv3_render_8bpp(uint32_t vram_start, nv3_coord_16_t screen_size)
{
if (!nv3)
return;
uint32_t vram_current_position = vram_start;
uint32_t* p;
uint32_t data = 0;
p = &nv3->nvbase.svga.monitor->target_buffer->line[0][0];
for (uint32_t y = 0; y < screen_size.y; y++)
{
for (uint32_t x = 0; x < screen_size.x; x++)
{
if (vram_current_position >= nv3->nvbase.vram_amount)
return;
p = &nv3->nvbase.svga.monitor->target_buffer->line[y][x];
data = *(uint32_t*)&nv3->nvbase.svga.vram[vram_current_position];
/* should just "tip over" to the next line */
*p = nv3_render_get_palette_index(data & 0xFF);
vram_current_position++;
}
}
}
/*
Blit a certain region from the (destination buffer base + (position in vram)) to the 86Box monitor, 15 bits per pixel format
*/
void nv3_render_15bpp(uint32_t vram_start, nv3_coord_16_t screen_size)
{
if (!nv3)
return;
uint32_t vram_current_position = vram_start;
uint32_t* p;
uint32_t data = 0;
p = &nv3->nvbase.svga.monitor->target_buffer->line[0][0];
for (uint32_t y = 0; y < screen_size.y; y++)
{
for (uint32_t x = 0; x < screen_size.x; x++)
{
if (vram_current_position >= nv3->nvbase.vram_amount)
return;
p = &nv3->nvbase.svga.monitor->target_buffer->line[y][x];
data = *(uint32_t*)&nv3->nvbase.svga.vram[vram_current_position];
/* should just "tip over" to the next line */
*p = nv3->nvbase.svga.conv_16to32(&nv3->nvbase.svga, data & 0xFFFF, 15);
vram_current_position += 2;
}
}
}
/*
Blit a certain region from the (destination buffer base + (position in vram)) to the 86Box monitor, 16 bits per pixel format
*/
void nv3_render_16bpp(uint32_t vram_start, nv3_coord_16_t screen_size)
{
if (!nv3)
return;
uint32_t vram_current_position = vram_start;
uint32_t* p;
uint32_t data = 0;
p = &nv3->nvbase.svga.monitor->target_buffer->line[0][0];
for (uint32_t y = 0; y < screen_size.y; y++)
{
for (uint32_t x = 0; x < screen_size.x; x++)
{
if (vram_current_position >= nv3->nvbase.vram_amount)
return;
p = &nv3->nvbase.svga.monitor->target_buffer->line[y][x];
data = *(uint32_t*)&nv3->nvbase.svga.vram[vram_current_position];
/* should just "tip over" to the next line */
*p = nv3->nvbase.svga.conv_16to32(&nv3->nvbase.svga, data & 0xFFFF, 15);
vram_current_position += 2;
}
}
}
/*
Blit a certain region from the (destination buffer base + (position in vram)) to the 86Box monitor, 32 bits per pixel format
*/
void nv3_render_32bpp(uint32_t vram_start, nv3_coord_16_t screen_size)
{
if (!nv3)
return;
uint32_t vram_current_position = vram_start;
uint32_t* p;
uint32_t data = 0;
p = &nv3->nvbase.svga.monitor->target_buffer->line[0][0];
for (uint32_t y = 0; y < screen_size.y; y++)
{
for (uint32_t x = 0; x < screen_size.x; x++)
{
if (vram_current_position >= nv3->nvbase.vram_amount)
return;
p = &nv3->nvbase.svga.monitor->target_buffer->line[y][x];
data = *(uint32_t*)&nv3->nvbase.svga.vram[vram_current_position];
/* should just "tip over" to the next line */
*p = data;
vram_current_position += 4;
}
}
}
// Translate an "NV-ROP" into a GDI Ternary ROP
uint8_t nv3_render_translate_nvrop(nv3_grobj_t grobj, uint32_t rop)
{
// Credit to envytools for this function:
// https://github.com/envytools/envytools/blob/f102b82381f3f11cee113d16374c87091db039d9/nvhw/pgraph.c
// How does one even go about reverse engineering this (I'm sure the behaviour is simpler when you don't have to translate this but...) Marcelina is a legend.
uint32_t patch_config_rop = (grobj.grobj_0 >> NV3_PGRAPH_CTX_SWITCH_PATCH_CONFIG) & 0x1F;
/* || patch_config_rop == NV3_PGRAPH_CTX_SWITCH_PATCH_CONFIG_RSVD0*/
// TODO: Blending
if (patch_config_rop == NV3_PGRAPH_CTX_SWITCH_PATCH_CONFIG_SRC_BYPASS) // 0x00 is used for "nothing here" it seems.
return VIDEO_ROP_SRC_COPY;
uint8_t res = 0;
int32_t swizzle[3];
if (patch_config_rop < 8) {
swizzle[0] = patch_config_rop >> 0 & 1;
swizzle[1] = patch_config_rop >> 1 & 1;
swizzle[2] = patch_config_rop >> 2 & 1;
} else if (patch_config_rop < 0x10) {
swizzle[0] = (patch_config_rop >> 0 & 1) + 1;
swizzle[1] = (patch_config_rop >> 1 & 1) + 1;
swizzle[2] = (patch_config_rop >> 2 & 1) + 1;
} else if (patch_config_rop == NV3_PGRAPH_CTX_SWITCH_PATCH_CONFIG_PAT_SRC_DST) {
swizzle[0] = 0, swizzle[1] = 1, swizzle[2] = 2;
} else if (patch_config_rop == NV3_PGRAPH_CTX_SWITCH_PATCH_CONFIG_PAT_DST_SRC) {
swizzle[0] = 1, swizzle[1] = 0, swizzle[2] = 2;
} else if (patch_config_rop == NV3_PGRAPH_CTX_SWITCH_PATCH_CONFIG_SRC_PAT_DST) {
swizzle[0] = 0, swizzle[1] = 2, swizzle[2] = 1;
} else if (patch_config_rop == NV3_PGRAPH_CTX_SWITCH_PATCH_CONFIG_SRC_DST_PAT) {
swizzle[0] = 2, swizzle[1] = 0, swizzle[2] = 1;
} else if (patch_config_rop == NV3_PGRAPH_CTX_SWITCH_PATCH_CONFIG_DST_PAT_SRC) {
swizzle[0] = 1, swizzle[1] = 2, swizzle[2] = 0;
} else if (patch_config_rop == NV3_PGRAPH_CTX_SWITCH_PATCH_CONFIG_DST_SRC_PAT) {
swizzle[0] = 2, swizzle[1] = 1, swizzle[2] = 0;
} else {
warning("NV3 ROP: Invalid patch configuration %02x!", rop);
}
if (patch_config_rop == 0) {
if (rop & 0x01)
res |= 0x11;
if (rop & 0x16)
res |= 0x44;
if (rop & 0x68)
res |= 0x22;
if (rop & 0x80)
res |= 0x88;
} else if (patch_config_rop == 0xf) {
if (rop & 0x01)
res |= 0x03;
if (rop & 0x16)
res |= 0x0c;
if (rop & 0x68)
res |= 0x30;
if (rop & 0x80)
res |= 0xc0;
} else {
int32_t i;
for (i = 0; i < 8; i++) {
int32_t s0 = i >> swizzle[0] & 1;
int32_t s1 = i >> swizzle[1] & 1;
int32_t s2 = i >> swizzle[2] & 1;
int32_t s = s2 << 2 | s1 << 1 | s0;
if (rop >> s & 1)
res |= 1 << i;
}
}
return res;
}

View File

@@ -0,0 +1,355 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* NV3 code to render basic objects.
*
*
*
* Authors: Connor Hyde, <mario64crashed@gmail.com> I need a better email address ;^)
*
* Copyright 2024-2025 Connor Hyde
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/pci.h>
#include <86box/rom.h>
#include <86box/video.h>
#include <86box/nv/vid_nv.h>
#include <86box/nv/vid_nv3.h>
#include <86box/utils/video_stdlib.h>
void nv3_render_rect(nv3_coord_16_t position, nv3_coord_16_t size, uint32_t color, nv3_grobj_t grobj)
{
nv3_coord_16_t current_pos = {0};
for (int32_t y = position.y; y < (position.y + size.y); y++)
{
current_pos.y = y;
for (int32_t x = position.x; x < (position.x + size.x); x++)
{
current_pos.x = x;
nv3_render_write_pixel(current_pos, color, grobj);
}
}
}
/* Render GDI-B clipped rectangle */
void nv3_render_rect_clipped(nv3_clip_16_t clip, uint32_t color, nv3_grobj_t grobj)
{
nv3_coord_16_t current_pos = {0};
for (int32_t y = clip.top; y < clip.bottom; y++)
{
current_pos.y = y;
for (int32_t x = clip.left; x < clip.right; x++)
{
current_pos.x = x;
/* compare against the global clip too */
if (current_pos.x >= nv3->pgraph.win95_gdi_text.clip_b.left
&& current_pos.x <= nv3->pgraph.win95_gdi_text.clip_b.right
&& current_pos.y >= nv3->pgraph.win95_gdi_text.clip_b.top
&& current_pos.y <= nv3->pgraph.win95_gdi_text.clip_b.bottom)
{
nv3_render_write_pixel(current_pos, color, grobj);
}
}
}
}
void nv3_render_gdi_transparent_bitmap_blit(bool bit, bool clip, uint32_t color, nv3_grobj_t grobj)
{
/* If the bit is set, and cliping is enabled (Type D) tru and lcip */
if (bit && clip)
{
/* Turn the bit off if we need to clip (Type D ) */
if (nv3->pgraph.win95_gdi_text_current_position.x < nv3->pgraph.win95_gdi_text.clip_d.left
|| nv3->pgraph.win95_gdi_text_current_position.x > nv3->pgraph.win95_gdi_text.clip_d.right
|| nv3->pgraph.win95_gdi_text_current_position.y < nv3->pgraph.win95_gdi_text.clip_d.top
|| nv3->pgraph.win95_gdi_text_current_position.y > nv3->pgraph.win95_gdi_text.clip_d.bottom)
{
bit = false;
}
/*
Also clip if we are outside of the SIZE_OUT range
We only need to do this in one direction just to get rid of the crud sent by NV
*/
uint32_t clip_x = nv3->pgraph.win95_gdi_text.point_d.x + (nv3->pgraph.win95_gdi_text.size_out_d.x);
uint32_t clip_y = nv3->pgraph.win95_gdi_text.point_d.y + (nv3->pgraph.win95_gdi_text.size_out_d.y);
if (nv3->pgraph.win95_gdi_text_current_position.x >= clip_x
|| nv3->pgraph.win95_gdi_text_current_position.y >= clip_y)
bit = false;
}
/* We don't need to and it, because it seems the Riva only uses non-packed bpp formats for this class */
if (bit)
nv3_render_write_pixel(nv3->pgraph.win95_gdi_text_current_position, color, grobj);
/*
Check if we've reached the bottom
Because we check the bits in reverse, we go forward (bits 7,6,5 were set for a 1x3 bitmap)
*/
uint32_t end_x = (clip) ? nv3->pgraph.win95_gdi_text.point_d.x + nv3->pgraph.win95_gdi_text.size_in_d.x : nv3->pgraph.win95_gdi_text.point_c.x + nv3->pgraph.win95_gdi_text.size_c.x;
nv3->pgraph.win95_gdi_text_current_position.x++;
if (nv3->pgraph.win95_gdi_text_current_position.x >= end_x)
{
nv3->pgraph.win95_gdi_text_current_position.y++;
if (!clip)
nv3->pgraph.win95_gdi_text_current_position.x = nv3->pgraph.win95_gdi_text.point_c.x;
else
nv3->pgraph.win95_gdi_text_current_position.x = nv3->pgraph.win95_gdi_text.point_d.x;
}
}
/* Originally written 23 March 2025, but then, redone, properly, on 30 March 2025 */
void nv3_render_gdi_transparent_bitmap(bool clip, uint32_t color, uint32_t bitmap_data, nv3_grobj_t grobj)
{
/*
First, we need to figure out how many bits we have left.
If we have less than 32, don't process all of the bits.
Bits are processed in the following order: [7-0] [15-8] [23-16] [31-24]
TODO: Store this somewhere, so it doesn't need to be recalculated.
We store a global bit count for this purpose.
*/
uint32_t bitmap_size = (clip) ? nv3->pgraph.win95_gdi_text.size_in_d.x * nv3->pgraph.win95_gdi_text.size_in_d.y : nv3->pgraph.win95_gdi_text.size_c.x * nv3->pgraph.win95_gdi_text.size_c.y;
uint32_t bits_remaining_in_bitmap = bitmap_size - nv3->pgraph.win95_gdi_text_bit_count;
/* we have to interpret every bit in reverse bit order but in the right byte order */
bool current_bit = false;
/* Start by rendering bits 7 through 0 */
for (int32_t bit = 7; bit >= 0; bit--)
{
current_bit = (bitmap_data >> bit) & 0x01;
nv3_render_gdi_transparent_bitmap_blit(current_bit, clip, color, grobj);
nv3->pgraph.win95_gdi_text_bit_count++;
bits_remaining_in_bitmap--;
if (!bits_remaining_in_bitmap)
break;
}
/* IF we're done, let's return */
if (!bits_remaining_in_bitmap)
return;
/* Now for 15 through 8 */
for (int32_t bit = 15; bit >= 8; bit--)
{
current_bit = (bitmap_data >> bit) & 0x01;
nv3_render_gdi_transparent_bitmap_blit(current_bit, clip, color, grobj);
nv3->pgraph.win95_gdi_text_bit_count++;
bits_remaining_in_bitmap--;
if (!bits_remaining_in_bitmap)
break;
}
/* IF we're done, let's return */
if (!bits_remaining_in_bitmap)
return;
/* Now for 23 through 16 */
for (int32_t bit = 23; bit >= 16; bit--)
{
current_bit = (bitmap_data >> bit) & 0x01;
nv3_render_gdi_transparent_bitmap_blit(current_bit, clip, color, grobj);
nv3->pgraph.win95_gdi_text_bit_count++;
bits_remaining_in_bitmap--;
if (!bits_remaining_in_bitmap)
break;
}
/* IF we're done, let's return */
if (!bits_remaining_in_bitmap)
return;
/* Now for 31 through 24 */
for (int32_t bit = 31; bit >= 24; bit--)
{
current_bit = (bitmap_data >> bit) & 0x01;
nv3_render_gdi_transparent_bitmap_blit(current_bit, clip, color, grobj);
nv3->pgraph.win95_gdi_text_bit_count++;
bits_remaining_in_bitmap--;
if (!bits_remaining_in_bitmap)
break;
}
/* IF we're done, let's return */
if (!bits_remaining_in_bitmap)
return;
}
void nv3_render_gdi_1bpp_bitmap_blit(bool bit, uint32_t color0, uint32_t color1, nv3_grobj_t grobj)
{
/* We can't force the bit off because this is a 1bpp bitmap */
bool skip = false;
/* For Type E, always clip */
/* Turn the bit off if we need to clip (Type D ) */
if (nv3->pgraph.win95_gdi_text_current_position.x < nv3->pgraph.win95_gdi_text.clip_e.left
|| nv3->pgraph.win95_gdi_text_current_position.x > nv3->pgraph.win95_gdi_text.clip_e.right
|| nv3->pgraph.win95_gdi_text_current_position.y < nv3->pgraph.win95_gdi_text.clip_e.top
|| nv3->pgraph.win95_gdi_text_current_position.y > nv3->pgraph.win95_gdi_text.clip_e.bottom)
{
skip = true;
}
/*
Also clip if we are outside of the SIZE_OUT range
We only need to do this in one direction just to get rid of the crud sent by NV
*/
uint32_t clip_x = nv3->pgraph.win95_gdi_text.point_e.x + (nv3->pgraph.win95_gdi_text.size_out_e.x);
uint32_t clip_y = nv3->pgraph.win95_gdi_text.point_e.y + (nv3->pgraph.win95_gdi_text.size_out_e.y);
if (nv3->pgraph.win95_gdi_text_current_position.x >= clip_x
|| nv3->pgraph.win95_gdi_text_current_position.y >= clip_y)
skip = true;
/* We don't need to and it, because it seems the Riva only uses non-packed bpp formats for this class */
if (!skip)
{
if (bit)
nv3_render_write_pixel(nv3->pgraph.win95_gdi_text_current_position, nv3->pgraph.win95_gdi_text.color1_e, grobj);
else
nv3_render_write_pixel(nv3->pgraph.win95_gdi_text_current_position, nv3->pgraph.win95_gdi_text.color0_e, grobj);
}
/*
Check if we've reached the bottom, if so, advance to the next horizontal lin
Because we check the bits in reverse, we go forward (bits 7,6,5 were set for a 1x3 bitmap)
*/
uint32_t end_x = nv3->pgraph.win95_gdi_text.point_e.x + nv3->pgraph.win95_gdi_text.size_in_e.x;
nv3->pgraph.win95_gdi_text_current_position.x++;
if (nv3->pgraph.win95_gdi_text_current_position.x >= end_x)
{
nv3->pgraph.win95_gdi_text_current_position.y++;
nv3->pgraph.win95_gdi_text_current_position.x = nv3->pgraph.win95_gdi_text.point_e.x;
}
}
/* Originally written 23 March 2025, but then, redone, properly, on 30 March 2025 */
void nv3_render_gdi_1bpp_bitmap(uint32_t color0, uint32_t color1, uint32_t bitmap_data, nv3_grobj_t grobj)
{
/*
First, we need to figure out how many bits we have left.
If we have less than 32, don't process all of the bits.
Bits are processed in the following order: [7-0] [15-8] [23-16] [31-24]
TODO: Store this somewhere, so it doesn't need to be recalculated.
We store a global bit count for this purpose.
*/
uint32_t bitmap_size = nv3->pgraph.win95_gdi_text.size_in_e.x * nv3->pgraph.win95_gdi_text.size_in_e.y;
uint32_t bits_remaining_in_bitmap = bitmap_size - nv3->pgraph.win95_gdi_text_bit_count;
/* we have to interpret every bit in reverse bit order but in the right byte order */
bool current_bit = false;
/* Start by rendering bits 7 through 0 */
for (int32_t bit = 7; bit >= 0; bit--)
{
current_bit = (bitmap_data >> bit) & 0x01;
nv3_render_gdi_1bpp_bitmap_blit(current_bit, color0, color1, grobj);
nv3->pgraph.win95_gdi_text_bit_count++;
bits_remaining_in_bitmap--;
if (!bits_remaining_in_bitmap)
break;
}
/* IF we're done, let's return */
if (!bits_remaining_in_bitmap)
return;
/* Now for 15 through 8 */
for (int32_t bit = 15; bit >= 8; bit--)
{
current_bit = (bitmap_data >> bit) & 0x01;
nv3_render_gdi_1bpp_bitmap_blit(current_bit, color0, color1, grobj);
nv3->pgraph.win95_gdi_text_bit_count++;
bits_remaining_in_bitmap--;
if (!bits_remaining_in_bitmap)
break;
}
/* IF we're done, let's return */
if (!bits_remaining_in_bitmap)
return;
/* Now for 23 through 16 */
for (int32_t bit = 23; bit >= 16; bit--)
{
current_bit = (bitmap_data >> bit) & 0x01;
nv3_render_gdi_1bpp_bitmap_blit(current_bit, color0, color1, grobj);
nv3->pgraph.win95_gdi_text_bit_count++;
bits_remaining_in_bitmap--;
if (!bits_remaining_in_bitmap)
break;
}
/* IF we're done, let's return */
if (!bits_remaining_in_bitmap)
return;
/* Now for 31 through 24 */
for (int32_t bit = 31; bit >= 24; bit--)
{
current_bit = (bitmap_data >> bit) & 0x01;
nv3_render_gdi_1bpp_bitmap_blit(current_bit, color0, color1, grobj);
nv3->pgraph.win95_gdi_text_bit_count++;
bits_remaining_in_bitmap--;
if (!bits_remaining_in_bitmap)
break;
}
/* IF we're done, let's return */
if (!bits_remaining_in_bitmap)
return;
}

View File

@@ -0,0 +1,255 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* NV3 PBUS: 128-bit unified bus
*
*
*
* Authors: Connor Hyde, <mario64crashed@gmail.com> I need a better email dataess ;^)
*
* Copyright 2024-2025 starfrost
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/pci.h>
#include <86box/rom.h> // DEPENDENT!!!
#include <86box/video.h>
#include <86box/nv/vid_nv.h>
#include <86box/nv/vid_nv3.h>
// NV3 PBUS RMA - Real Mode Access for VBIOS
// This basically works like a shifter, you write one byte at a time from [0x3d0...0x3d3] and it shifts it in to build a 32-bit MMIO address...
// Putting this in pbus because imo it makes the most sense (related to memory access/memory interface)
nv_register_t pbus_registers[] = {
{ NV3_PBUS_DEBUG_0, "PBUS - Debug Register", NULL, NULL},
{ NV3_PBUS_INTR, "PBUS - Interrupt Status", NULL, NULL},
{ NV3_PBUS_INTR_EN, "PBUS - Interrupt Enable", NULL, NULL,},
{ NV_REG_LIST_END, NULL, NULL, NULL}, // sentinel value
};
void nv3_pbus_init(void)
{
nv_log("Initialising PBUS...");
nv_log("Done\n");
}
uint32_t nv3_pbus_read(uint32_t address)
{
nv_register_t* reg = nv_get_register(address, pbus_registers, sizeof(pbus_registers)/sizeof(pbus_registers[0]));
uint32_t ret = 0x00;
// todo: friendly logging
nv_log_verbose_only("PBUS Read from 0x%08x", address);
// if the register actually exists
if (reg)
{
// on-read function
if (reg->on_read)
ret = reg->on_read();
else
{
switch (reg->address)
{
case NV3_PBUS_DEBUG_0:
ret = nv3->pbus.debug_0;
break;
case NV3_PBUS_INTR:
ret = nv3->pbus.interrupt_status;
break;
case NV3_PBUS_INTR_EN:
ret = nv3->pbus.interrupt_enable;
break;
}
}
if (reg->friendly_name)
nv_log_verbose_only(": 0x%08x <- %s\n", ret, reg->friendly_name);
else
nv_log_verbose_only("\n");
}
else
{
nv_log(": Unknown register read (address=0x%08x), returning 0x00\n", address);
}
return ret;
}
void nv3_pbus_write(uint32_t address, uint32_t value)
{
nv_register_t* reg = nv_get_register(address, pbus_registers, sizeof(pbus_registers)/sizeof(pbus_registers[0]));
nv_log_verbose_only("PBUS Write 0x%08x -> 0x%08x\n", value, address);
// if the register actually exists
if (reg)
{
if (reg->friendly_name)
nv_log_verbose_only(": %s\n", reg->friendly_name);
else
nv_log_verbose_only("\n");
// on-read function
if (reg->on_write)
reg->on_write(value);
else
{
switch (reg->address)
{
case NV3_PBUS_DEBUG_0:
nv3->pbus.debug_0 = value;
break;
// Interrupt registers
// Interrupt state:
// Bit 0 - PCI Bus Error
case NV3_PBUS_INTR:
nv3->pbus.interrupt_status &= ~value;
nv3_pmc_clear_interrupts();
break;
case NV3_PBUS_INTR_EN:
nv3->pbus.interrupt_enable = value & 0x00000001;
break;
}
}
}
else /* Completely unknown */
{
nv_log(": Unknown register write (address=0x%08x)\n", address);
}
}
uint8_t nv3_pbus_rma_read(uint16_t addr)
{
addr &= 0xFF;
uint32_t real_final_address = 0x0;
uint8_t ret = 0x0;
switch (addr)
{
// signature so you know reads work
case 0x00:
ret = NV3_RMA_SIGNATURE_MSB;
break;
case 0x01:
ret = NV3_RMA_SIGNATURE_BYTE2;
break;
case 0x02:
ret = NV3_RMA_SIGNATURE_BYTE1;
break;
case 0x03:
ret = NV3_RMA_SIGNATURE_LSB;
break;
case 0x08 ... 0x0B:
// reads must be dword aligned
real_final_address = (nv3->pbus.rma.addr + (addr & 0x03));
if (nv3->pbus.rma.addr < NV3_MMIO_SIZE)
ret = nv3_mmio_read8(real_final_address, NULL);
else
{
/* Do we need to read RAMIN here? */
ret = nv3->nvbase.svga.vram[real_final_address - NV3_MMIO_SIZE] & (nv3->nvbase.svga.vram_max - 1);
}
// log current location for vbios RE
nv_log_verbose_only("MMIO Real Mode Access read, initial address=0x%04x final RMA MMIO address=0x%08x data=0x%08x\n",
addr, real_final_address, ret);
break;
}
return ret;
}
// Implements a 32-bit write using 16 bit port number
void nv3_pbus_rma_write(uint16_t addr, uint8_t val)
{
// addresses are in reality 8bit so just mask it to be safe
addr &= 0xFF;
// format:
// 0x00 ID
// 0x04 Pointer to data
// 0x08 Data port(?)
// 0x0B Data - 32bit. SENT IN THE RIGHT ORDER FOR ONCE WAHOO!
// 0x10 Increment (?) data - implemented the same as data for now
if (addr < 0x08)
{
switch (addr % 0x04)
{
case 0x00: // lowest byte
nv3->pbus.rma.addr &= ~0xff;
nv3->pbus.rma.addr |= val;
break;
case 0x01: // 2nd highest byte
nv3->pbus.rma.addr &= ~0xff00;
nv3->pbus.rma.addr |= (val << 8);
break;
case 0x02: // 3rd highest byte
nv3->pbus.rma.addr &= ~0xff0000;
nv3->pbus.rma.addr |= (val << 16);
break;
case 0x03: // 4th highest byte
nv3->pbus.rma.addr &= ~0xff000000;
nv3->pbus.rma.addr |= (val << 24);
break;
}
}
// Data to send to MMIO
else
{
switch (addr % 0x04)
{
case 0x00: // lowest byte
nv3->pbus.rma.data &= ~0xff;
nv3->pbus.rma.data |= val;
break;
case 0x01: // 2nd highest byte
nv3->pbus.rma.data &= ~0xff00;
nv3->pbus.rma.data |= (val << 8);
break;
case 0x02: // 3rd highest byte
nv3->pbus.rma.data &= ~0xff0000;
nv3->pbus.rma.data |= (val << 16);
break;
case 0x03: // 4th highest byte
nv3->pbus.rma.data &= ~0xff000000;
nv3->pbus.rma.data |= (val << 24);
nv_log_verbose_only("MMIO Real Mode Access write transaction complete, initial address=0x%04x final RMA MMIO address=0x%08x data=0x%08x\n",
addr, nv3->pbus.rma.addr, nv3->pbus.rma.data);
if (nv3->pbus.rma.addr < NV3_MMIO_SIZE)
nv3_mmio_write32(nv3->pbus.rma.addr, nv3->pbus.rma.data, NULL);
else // failsafe code, i don't think you will ever write outside of VRAM?
{
uint32_t* vram_32 = (uint32_t*)nv3->nvbase.svga.vram;
vram_32[(nv3->pbus.rma.addr - NV3_MMIO_SIZE) >> 2] = nv3->pbus.rma.data;
}
break;
}
}
if (addr & 0x10)
nv3->pbus.rma.addr += 0x04; // Alignment
}

View File

@@ -0,0 +1,274 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* NV3 PBUS DMA: DMA & Notifier Engine
*
*
*
* Authors: Connor Hyde, <mario64crashed@gmail.com> I need a better email address ;^)
*
* Copyright 2024-2025 starfrost
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/dma.h>
#include <86box/mem.h>
#include <86box/pci.h>
#include <86box/rom.h> // DEPENDENT!!!
#include <86box/video.h>
#include <86box/nv/vid_nv.h>
#include <86box/nv/vid_nv3.h>
/* Nvidia DMA Engine */
void nv3_perform_dma_m2mf(nv3_grobj_t grobj)
{
// notify object base=grobj_1 >> 12
uint32_t notify_obj_base = grobj.grobj_1 >> 12;
uint32_t notify_obj_info = nv3_ramin_read32(notify_obj_base, nv3);
uint32_t notify_obj_limit = nv3_ramin_read32(notify_obj_base + 0x04, nv3);
uint32_t notify_obj_page = nv3_ramin_read32(notify_obj_base + 0x08, nv3);
/* extract some important information*/
uint32_t info_adjust = notify_obj_info & 0xFFF;
bool info_pt_present = (notify_obj_info >> NV3_NOTIFICATION_PT_PRESENT) & 0x01;
uint8_t info_dma_target = (notify_obj_info >> NV3_NOTIFICATION_TARGET) & 0x03;
/* paging information */
bool page_is_present = notify_obj_page & 0x01;
bool page_is_readwrite = (notify_obj_page >> NV3_NOTIFICATION_PAGE_ACCESS);
uint32_t frame_base = notify_obj_page & 0xFFFFF000;
// This code is temporary and will probably be moved somewhere else
// Print torns of debug info
#ifdef DEBUG
nv_log_verbose_only("******* WARNING: IF THIS OPERATION FUCKS UP, RANDOM MEMORY WILL BE CORRUPTED, YOUR ENTIRE SYSTEM MAY BE HOSED *******\n");
nv_log_verbose_only("M2MF DMA Information:\n");
nv_log_verbose_only("Adjust Value: 0x%08x\n", info_adjust);
(info_pt_present) ? nv_log_verbose_only("Pagetable Present: True\n") : nv_log_verbose_only("Pagetable Present: False\n");
switch (info_dma_target)
{
case NV3_DMA_TARGET_NODE_VRAM:
nv_log_verbose_only("Notification Target: VRAM\n");
break;
case NV3_DMA_TARGET_NODE_CART:
nv_log_verbose_only("VERY BAD WARNING: Notification detected with Notification Target: Cartridge. THIS SHOULD NEVER HAPPEN!!!!!\n");
break;
case NV3_DMA_TARGET_NODE_PCI:
(nv3->nvbase.bus_generation == nv_bus_pci) ? nv_log_verbose_only("Notification Target: PCI Bus\n") : nv_log_verbose_only("Notification Target: PCI Bus (On AGP card?)\n");
break;
case NV3_DMA_TARGET_NODE_AGP:
(nv3->nvbase.bus_generation == nv_bus_agp_1x
|| nv3->nvbase.bus_generation == nv_bus_agp_2x) ? nv_log_verbose_only("Notification Target: AGP Bus\n") : nv_log_verbose_only("Notification Target: AGP Bus (On PCI card?)\n");
break;
}
nv_log_verbose_only("Limit: 0x%08x\n", notify_obj_limit);
(page_is_present) ? nv_log_verbose_only("Page is present\n") : nv_log_verbose_only("Page is not present\n");
(page_is_readwrite) ? nv_log_verbose_only("Page is read-write\n") : nv_log_verbose_only("Page is read-only\n");
nv_log_verbose_only("Pageframe Address: 0x%08x\n", frame_base);
#endif
// set up the dma transfer. we need to translate to a physical address.
uint32_t final_address = 0;
/* M2MF DMA only uses HW type */
final_address = frame_base + info_adjust;
/* send the notification off */
nv_log("About to send M2MF DMA to 0x%08x (Check target)\n", final_address);
uint32_t offset_in = (nv3->pgraph.m2mf.offset_in);
uint32_t offset_out = (nv3->pgraph.m2mf.offset_out);
uint32_t pitch_in = nv3->pgraph.m2mf.pitch_in;
uint32_t pitch_out = nv3->pgraph.m2mf.pitch_out;
// pitch out surely can't be 0
if (pitch_out == 0)
pitch_out = pitch_in;
uint32_t bytes_per_scanline = nv3->pgraph.m2mf.scanline_length;
uint8_t increment_in = (nv3->pgraph.m2mf.format) & 0x07;
uint8_t increment_out = (nv3->pgraph.m2mf.format >> NV3_M2MF_FORMAT_INPUT) & 0x07;
for (uint32_t scanline = 0; scanline < nv3->pgraph.m2mf.num_scanlines; scanline++)
{
for (uint32_t pixel = offset_in; pixel < (offset_in + bytes_per_scanline); pixel += increment_in)
{
nv3->nvbase.svga.vram[offset_out] = nv3->nvbase.svga.vram[offset_in];
offset_out += increment_out;
}
offset_in += pitch_in;
offset_out += pitch_out;
}
/*
switch (info_dma_target)
{
// for M2MF only NVM target node is used.
case NV3_DMA_TARGET_NODE_VRAM:
uint32_t* vram_32 = (uint32_t*)nv3->nvbase.svga.vram;
break;
case NV3_DMA_TARGET_NODE_PCI:
case NV3_DMA_TARGET_NODE_AGP:
// Idk how to implement increments of more than 1 but only 1 increments seem to be used with these.
uint32_t size_in = nv3->pgraph.m2mf.num_scanlines * nv3->pgraph.m2mf.pitch_in;
uint32_t size_out = nv3->pgraph.m2mf.num_scanlines * nv3->pgraph.m2mf.pitch_out;
uint8_t* page_in = calloc(1, size_in);
for (uint32_t scanline = 0; scanline < nv3->pgraph.m2mf.num_scanlines; scanline++)
{
}
dma_bm_read(offset_in, page_in, size_in, size_in);
dma_bm_write(offset_out, page_in, size_out, size_out);
break;
}
*/
// we're done
nv3->pgraph.notify_pending = false;
}
/* Sees if any notification is required after an object method is executed. If so, executes it... */
void nv3_notify_if_needed(uint32_t name, uint32_t method_id, nv3_ramin_context_t context, nv3_grobj_t grobj)
{
if (!nv3->pgraph.notify_pending)
return;
uint32_t current_notification_object = nv3->pgraph.notifier;
uint32_t notification_type = ((current_notification_object >> NV3_PGRAPH_NOTIFY_REQUEST_TYPE) & 0x07);
// check for a software method (0 = hardware, 1 = software)
if (notification_type != 0)
{
nv_log("Software Notification, firing interrupt");
nv3_pgraph_interrupt_valid(NV3_PGRAPH_INTR_0_SOFTWARE_NOTIFY);
//return;
}
// set up the NvNotification structure
nv3_notification_t notify = {0};
notify.nanoseconds = nv3->ptimer.time;
notify.status = NV3_NOTIFICATION_STATUS_DONE_OK; // it should be fine to just signal that it's ok
// these are only nonzero when there is an error
notify.info32 = notify.info16 = 0;
// notify object base=grobj_1 >> 12
uint32_t notify_obj_base = grobj.grobj_1 >> 12;
uint32_t notify_obj_info = nv3_ramin_read32(notify_obj_base, nv3);
uint32_t notify_obj_limit = nv3_ramin_read32(notify_obj_base + 0x04, nv3);
uint32_t notify_obj_page = nv3_ramin_read32(notify_obj_base + 0x08, nv3);
/* extract some important information*/
uint32_t info_adjust = notify_obj_info & 0xFFF;
bool info_pt_present = (notify_obj_info >> NV3_NOTIFICATION_PT_PRESENT) & 0x01;
uint8_t info_notification_target = (notify_obj_info >> NV3_NOTIFICATION_TARGET) & 0x03;
/* paging information */
bool page_is_present = notify_obj_page & 0x01;
bool page_is_readwrite = (notify_obj_page >> NV3_NOTIFICATION_PAGE_ACCESS);
uint32_t frame_base = notify_obj_page & 0xFFFFF000;
// This code is temporary and will probably be moved somewhere else
// Print torns of debug info
#ifdef DEBUG
nv_log_verbose_only("******* WARNING: IF THIS OPERATION FUCKS UP, RANDOM MEMORY WILL BE CORRUPTED, YOUR ENTIRE SYSTEM MAY BE HOSED *******\n");
nv_log_verbose_only("Notification Information:\n");
nv_log_verbose_only("Adjust Value: 0x%08x\n", info_adjust);
(info_pt_present) ? nv_log_verbose_only("Pagetable Present: True\n") : nv_log_verbose_only("Pagetable Present: False\n");
switch (info_notification_target)
{
case NV3_DMA_TARGET_NODE_VRAM:
nv_log_verbose_only("Notification Target: VRAM\n");
break;
case NV3_DMA_TARGET_NODE_CART:
nv_log_verbose_only("VERY BAD WARNING: Notification detected with Notification Target: Cartridge. THIS SHOULD NEVER HAPPEN!!!!!\n");
break;
case NV3_DMA_TARGET_NODE_PCI:
(nv3->nvbase.bus_generation == nv_bus_pci) ? nv_log_verbose_only("Notification Target: PCI Bus\n") : nv_log_verbose_only("Notification Target: PCI Bus (On AGP card?)\n");
break;
case NV3_DMA_TARGET_NODE_AGP:
(nv3->nvbase.bus_generation == nv_bus_agp_1x
|| nv3->nvbase.bus_generation == nv_bus_agp_2x) ? nv_log_verbose_only("Notification Target: AGP Bus\n") : nv_log_verbose_only("Notification Target: AGP Bus (On PCI card?)\n");
break;
}
nv_log_verbose_only("Limit: 0x%08x\n", notify_obj_limit);
(page_is_present) ? nv_log_verbose_only("Page is present\n") : nv_log_verbose_only("Page is not present\n");
(page_is_readwrite) ? nv_log_verbose_only("Page is read-write\n") : nv_log_verbose_only("Page is read-only\n");
nv_log_verbose_only("Pageframe Address: 0x%08x\n", frame_base);
#endif
// set up the dma transfer. we need to translate to a physical address.
uint32_t final_address = 0;
/* Simple case: hardware notification, we can just take the pte since it's based on the type */
if (notification_type == 0)
{
final_address = frame_base + info_adjust;
}
else
{
// for software we have to calculate the pte index
uint32_t pte_num = ((notification_type << 4) + info_adjust) >> 12;
/* ramin entries are sorted - 1 object for each pte entry...*/
final_address = nv3_ramin_read32(notify_obj_base + (0x10 * pte_num) + 8, nv3);
final_address += (info_adjust & 0xFFF);
}
/* send the notification off */
nv_log("About to send hardware notification to 0x%08x (Check target)\n", final_address);
switch (info_notification_target)
{
case NV3_DMA_TARGET_NODE_VRAM:
uint32_t* vram_32 = (uint32_t*)nv3->nvbase.svga.vram;
// increment by 1 because each index increments by 4
vram_32[final_address] = (notify.nanoseconds & 0xFFFFFFFF);
vram_32[final_address + 1] = (notify.nanoseconds >> 32);
vram_32[final_address + 2] = notify.info32;
vram_32[final_address + 3] = (notify.info16 | notify.status);
break;
case NV3_DMA_TARGET_NODE_PCI:
case NV3_DMA_TARGET_NODE_AGP:
dma_bm_write(final_address, (uint8_t*)&notify, sizeof(nv3_notification_t), 4);
break;
}
// we're done
nv3->pgraph.notify_pending = false;
}

View File

@@ -0,0 +1,161 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* NV3 PEXTDEV - External Devices
* Including straps
*
*
*
* Authors: Connor Hyde, <mario64crashed@gmail.com> I need a better email address ;^)
*
* Copyright 2024-2025 starfrost
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/pci.h>
#include <86box/rom.h> // DEPENDENT!!!
#include <86box/video.h>
#include <86box/nv/vid_nv.h>
#include <86box/nv/vid_nv3.h>
void nv3_pextdev_init(void)
{
nv_log("Initialising PEXTDEV....\n");
// Set the chip straps
// Make these configurable in the future...
// Current settings
// AGP2X Disabled
// TV Mode NTSC
// Crystal 13.5 Mhz
// Bus width 128-Bit (some gpus were sold as 64bit for cost reduction)
//
nv_log("Initialising straps...\n");
nv3->pextdev.straps =
(NV3_PSTRAPS_AGP2X_DISABLED << NV3_PSTRAPS_AGP2X) |
(NV3_PSTRAPS_TVMODE_NTSC << NV3_PSTRAPS_TVMODE) |
(NV3_PSTRAPS_CRYSTAL_13500K << NV3_PSTRAPS_CRYSTAL);
// figure out the bus
if (nv3->nvbase.bus_generation == nv_bus_pci)
nv3->pextdev.straps |= (NV3_PSTRAPS_BUS_TYPE_PCI << NV3_PSTRAPS_BUS_TYPE);
else
nv3->pextdev.straps |= (NV3_PSTRAPS_BUS_TYPE_AGP << NV3_PSTRAPS_BUS_TYPE);
// now the lower bits
nv3->pextdev.straps |=
(NV3_PSTRAPS_BUS_WIDTH_128BIT << NV3_PSTRAPS_BUS_WIDTH) |
(NV3_PSTRAPS_BIOS_PRESENT << NV3_PSTRAPS_BIOS) |
(NV3_PSTRAPS_BUS_SPEED_66MHZ << NV3_PSTRAPS_BUS_SPEED);
nv_log("Straps = 0x%04x\n", nv3->pextdev.straps);
nv_log("Initialising PEXTDEV: Done\n");
}
//
// ****** PEXTDEV register list START ******
//
nv_register_t pextdev_registers[] = {
{ NV3_PSTRAPS, "Straps - Chip Configuration", NULL, NULL },
{ NV_REG_LIST_END, NULL, NULL, NULL }, // sentinel value
};
//
// ****** Read/Write functions start ******
//
uint32_t nv3_pextdev_read(uint32_t address)
{
nv_register_t* reg = nv_get_register(address, pextdev_registers, sizeof(pextdev_registers)/sizeof(pextdev_registers[0]));
uint32_t ret = 0x00;
// special consideration for straps
if (address == NV3_PSTRAPS)
{
nv_log_verbose_only("Straps Read (current value=0x%08x)\n", nv3->pextdev.straps);
}
else
{
nv_log_verbose_only("PEXTDEV Read from 0x%08x", address);
}
// if the register actually exists
if (reg)
{
// on-read function
if (reg->on_read)
ret = reg->on_read();
else
{
switch (reg->address)
{
case NV3_PSTRAPS:
ret = nv3->pextdev.straps;
break;
}
}
if (reg->friendly_name)
nv_log_verbose_only(": 0x%08x <- %s\n", ret, reg->friendly_name);
else
nv_log_verbose_only("\n");
}
else
{
nv_log(": Unknown register read (address=0x%08x), returning 0x00\n", address);
}
return ret;
}
void nv3_pextdev_write(uint32_t address, uint32_t value)
{
nv_register_t* reg = nv_get_register(address, pextdev_registers, sizeof(pextdev_registers)/sizeof(pextdev_registers[0]));
nv_log_verbose_only("PEXTDEV Write 0x%08x -> 0x%08x\n", value, address);
// special consideration for straps
if (address == NV3_PSTRAPS)
{
/* For some reason, all RIVA 128 ZX VBIOSes try to write to the straps. So only indicate this as a problem and return on Rev A/B */
if (nv3->nvbase.gpu_revision != NV3_PCI_CFG_REVISION_C00)
{
warning("Huh? Tried to write to the straps (value=%d). Something is wrong...\n", nv3->pextdev.straps);
return;
}
}
// if the register actually exists
if (reg)
{
if (reg->friendly_name)
nv_log_verbose_only(": %s\n", reg->friendly_name);
else
nv_log_verbose_only("\n");
// on-read function
if (reg->on_write)
reg->on_write(value);
}
else /* Completely unknown */
{
nv_log(": Unknown register write (address=0x%08x)\n", address);
}
}

View File

@@ -0,0 +1,204 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* NV3 PFB: Framebuffer
*
*
*
* Authors: Connor Hyde, <mario64crashed@gmail.com> I need a better email address ;^)
*
* Copyright 2024-2025 starfrost
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/pci.h>
#include <86box/rom.h> // DEPENDENT!!!
#include <86box/video.h>
#include <86box/nv/vid_nv.h>
#include <86box/nv/vid_nv3.h>
// Functions only used in this translation unit
uint32_t nv3_pfb_config0_read(void);
void nv3_pfb_config0_write(uint32_t val);
nv_register_t pfb_registers[] = {
{ NV3_PFB_BOOT, "PFB Boot Config", NULL, NULL},
{ NV3_PFB_DELAY, "PFB Delay", NULL, NULL},
{ NV3_PFB_DEBUG_0, "PFB Debug", NULL, NULL },
{ NV3_PFB_GREEN_0, "PFB Green / Power Saving", NULL, NULL,},
{ NV3_PFB_CONFIG_0, "PFB Framebuffer Config 0", nv3_pfb_config0_read, nv3_pfb_config0_write },
{ NV3_PFB_CONFIG_1, "PFB Framebuffer Config 1", NULL, NULL },
{ NV3_PFB_RTL, "PFB RTL (Part of memory timings)", NULL, NULL },
{ NV_REG_LIST_END, NULL, NULL, NULL}, // sentinel value
};
void nv3_pfb_init(void)
{
nv_log("Initialising PFB...");
// initial configuration:
// ram 4mb
// bus width 128bit
// extension ram none (was this ever used?)
// ram banks 4 (based on observation of physical RIVA 128 with 4mb, does 1 bank = 1chip?)
// twiddle off (check this on a real card once it's actually installed)
nv3->pfb.boot = (NV3_PFB_BOOT_RAM_EXTENSION_NONE << (NV3_PFB_BOOT_RAM_EXTENSION)
| (NV3_PFB_BOOT_RAM_DATA_TWIDDLE_OFF << NV3_PFB_BOOT_RAM_DATA_TWIDDLE)
| (NV3_PFB_BOOT_RAM_BANKS_4 << NV3_PFB_BOOT_RAM_BANKS)
| (NV3_PFB_BOOT_RAM_WIDTH_128 << NV3_PFB_BOOT_RAM_WIDTH)
);
if (nv3->nvbase.vram_amount == NV3_VRAM_SIZE_4MB)
nv3->pfb.boot |= (NV3_PFB_BOOT_RAM_AMOUNT_4MB << NV3_PFB_BOOT_RAM_AMOUNT);
else
nv3->pfb.boot |= (NV3_PFB_BOOT_RAM_AMOUNT_8MB << NV3_PFB_BOOT_RAM_AMOUNT);
nv_log("Done\n");
}
uint32_t nv3_pfb_read(uint32_t address)
{
nv_register_t* reg = nv_get_register(address, pfb_registers, sizeof(pfb_registers)/sizeof(pfb_registers[0]));
uint32_t ret = 0x00;
// todo: friendly logging
nv_log_verbose_only("PFB Read from 0x%08x", address);
// if the register actually exists
if (reg)
{
// on-read function
if (reg->on_read)
ret = reg->on_read();
else
{
switch (reg->address)
{
case NV3_PFB_BOOT:
ret = nv3->pfb.boot;
break;
case NV3_PFB_DEBUG_0:
ret = nv3->pfb.debug_0;
break;
// Config 0 has a read/write function
case NV3_PFB_CONFIG_1:
ret = nv3->pfb.config_1;
break;
case NV3_PFB_GREEN_0:
ret = nv3->pfb.green;
break;
case NV3_PFB_DELAY:
ret = nv3->pfb.delay;
break;
case NV3_PFB_RTL:
ret = nv3->pfb.rtl;
break;
}
}
if (reg->friendly_name)
nv_log_verbose_only(": 0x%08x <- %s\n", ret, reg->friendly_name);
else
nv_log_verbose_only("\n");
}
else
{
nv_log(": Unknown register read (address=0x%08x), returning 0x00\n", address);
}
return ret;
}
void nv3_pfb_write(uint32_t address, uint32_t value)
{
nv_register_t* reg = nv_get_register(address, pfb_registers, sizeof(pfb_registers)/sizeof(pfb_registers[0]));
nv_log_verbose_only("PFB Write 0x%08x -> 0x%08x", value, address);
// if the register actually exists
if (reg)
{
if (reg->friendly_name)
nv_log_verbose_only(": %s\n", reg->friendly_name);
else
nv_log_verbose_only("\n");
// on-read function
if (reg->on_write)
reg->on_write(value);
else
{
switch (reg->address)
{
// Boot is read only
case NV3_PFB_DEBUG_0:
nv3->pfb.debug_0 = value;
break;
// Config 0 has a read/write function
case NV3_PFB_CONFIG_1: // Config Register 1
nv3->pfb.config_1 = value;
break;
case NV3_PFB_GREEN_0:
nv3->pfb.green = value;
break;
case NV3_PFB_DELAY:
nv3->pfb.delay = value;
break;
case NV3_PFB_RTL:
nv3->pfb.rtl = value;
break;
}
}
}
else /* Completely unknown */
{
nv_log(": Unknown register write (address=0x%08x)\n", address);
}
}
uint32_t nv3_pfb_config0_read(void)
{
return nv3->pfb.config_0;
}
void nv3_pfb_config0_write(uint32_t val)
{
nv3->pfb.config_0 = val;
// i think the actual size and pixel depth are set in PRAMDAC
// so we don't update things here for now
uint32_t new_pfb_htotal = (nv3->pfb.config_0 & 0x3F) << 5;
// i don't think 16:9 is supported
uint32_t new_pfb_vtotal = new_pfb_htotal * (3.0/4.0);
uint32_t new_bit_depth = (nv3->pfb.config_0 >> 8) & 0x03;
// This doesn't actually seem very useful
nv_log_verbose_only("Framebuffer Config Change\n");
nv_log_verbose_only("Horizontal Size=%d pixels\n", new_pfb_htotal);
nv_log_verbose_only("Vertical Size @ 4:3=%d pixels\n", new_pfb_vtotal);
if (new_bit_depth == NV3_PFB_CONFIG_0_DEPTH_8BPP)
nv_log_verbose_only("Bit Depth=8bpp\n");
else if (new_bit_depth == NV3_PFB_CONFIG_0_DEPTH_16BPP)
nv_log_verbose_only("Bit Depth=16bpp\n");
else if (new_bit_depth == NV3_PFB_CONFIG_0_DEPTH_32BPP)
nv_log_verbose_only("Bit Depth=32bpp\n");
}

View File

@@ -0,0 +1,985 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* NV3 PFIFO (FIFO for graphics object submission)
* PIO object submission
* Gray code conversion routines
*
* Authors: Connor Hyde, <mario64crashed@gmail.com> I need a better email address ;^)
*
* Copyright 2024-2025 starfrost
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/dma.h>
#include <86box/mem.h>
#include <86box/pci.h>
#include <86box/rom.h> // DEPENDENT!!!
#include <86box/video.h>
#include <86box/nv/vid_nv.h>
#include <86box/nv/vid_nv3.h>
//
// ****** PFIFO register list START ******
//
nv_register_t pfifo_registers[] = {
{ NV3_PFIFO_INTR, "PFIFO - Interrupt Status", NULL, NULL},
{ NV3_PFIFO_INTR_EN, "PFIFO - Interrupt Enable", NULL, NULL,},
{ NV3_PFIFO_DELAY_0, "PFIFO - DMA Delay/Retry Register", NULL, NULL},
{ NV3_PFIFO_DEBUG_0, "PFIFO - Debug 0", NULL, NULL, },
{ NV3_PFIFO_CONFIG_0, "PFIFO - Config 0", NULL, NULL, },
{ NV3_PFIFO_CONFIG_RAMFC, "PFIFO - RAMIN RAMFC Config", NULL, NULL },
{ NV3_PFIFO_CONFIG_RAMHT, "PFIFO - RAMIN RAMHT Config", NULL, NULL },
{ NV3_PFIFO_CONFIG_RAMRO, "PFIFO - RAMIN RAMRO Config", NULL, NULL },
{ NV3_PFIFO_CACHE_REASSIGNMENT, "PFIFO - Allow Cache Channel Reassignment", NULL, NULL },
{ NV3_PFIFO_CACHE0_PULL0, "PFIFO - Cache0 Puller Control", NULL, NULL},
{ NV3_PFIFO_CACHE1_PULL0, "PFIFO - Cache1 Puller Control"},
{ NV3_PFIFO_CACHE0_PULLER_CTX_STATE, "PFIFO - Cache0 Puller State1 (Is context clean?)", NULL, NULL},
{ NV3_PFIFO_CACHE1_PULL0, "PFIFO - Cache1 Puller State0", NULL, NULL},
{ NV3_PFIFO_CACHE1_PULLER_CTX_STATE, "PFIFO - Cache1 Puller State1 (Is context clean?)", NULL, NULL},
{ NV3_PFIFO_CACHE0_PUSH_ENABLED, "PFIFO - Cache0 Access", NULL, NULL, },
{ NV3_PFIFO_CACHE1_PUSH_ENABLED, "PFIFO - Cache1 Access", NULL, NULL, },
{ NV3_PFIFO_CACHE0_PUSH_CHANNEL_ID, "PFIFO - Cache0 Push Channel ID", NULL, NULL, },
{ NV3_PFIFO_CACHE1_PUSH_CHANNEL_ID, "PFIFO - Cache1 Push Channel ID", NULL, NULL, },
{ NV3_PFIFO_CACHE0_ERROR_PENDING, "PFIFO - Cache0 DMA Error Pending?", NULL, NULL, },
{ NV3_PFIFO_CACHE0_STATUS, "PFIFO - Cache0 Status", NULL, NULL},
{ NV3_PFIFO_CACHE1_STATUS, "PFIFO - Cache1 Status", NULL, NULL},
{ NV3_PFIFO_CACHE0_GET, "PFIFO - Cache0 Get", NULL, NULL },
{ NV3_PFIFO_CACHE0_CTX, "PFIFO - Cache0 Context", NULL, NULL },
{ NV3_PFIFO_CACHE1_GET, "PFIFO - Cache1 Get", NULL, NULL },
{ NV3_PFIFO_CACHE0_PUT, "PFIFO - Cache0 Put", NULL, NULL },
{ NV3_PFIFO_CACHE1_PUT, "PFIFO - Cache1 Put", NULL, NULL },
//Cache1 exclusive stuff
{ NV3_PFIFO_CACHE1_DMA_CONFIG_0, "PFIFO - Cache1 DMA Access (bit 0: is running, bit 4: is busy)"},
{ NV3_PFIFO_CACHE1_DMA_CONFIG_1, "PFIFO - Cache1 DMA Length"},
{ NV3_PFIFO_CACHE1_DMA_CONFIG_2, "PFIFO - Cache1 DMA Address"},
{ NV3_PFIFO_CACHE1_DMA_CONFIG_3, "PFIFO - Cache1 DMA Target Node"},
{ NV3_PFIFO_CACHE1_DMA_STATUS, "PFIFO - Cache1 DMA Status"},
{ NV3_PFIFO_CACHE1_DMA_TLB_PT_BASE, "PFIFO - Cache1 DMA TLB - Pagetable Base"},
{ NV3_PFIFO_CACHE1_DMA_TLB_PTE, "PFIFO - Cache1 DMA TLB - Pagetable Entry (31:12 - Frame Address; bit 0 - Is Present)"},
{ NV3_PFIFO_CACHE1_DMA_TLB_TAG, "PFIFO - Cache1 DMA TLB - Tag"},
//Runout
{ NV3_PFIFO_RUNOUT_GET, "PFIFO Runout Get Address [8:3 if 512b, otherwise 12:3]"},
{ NV3_PFIFO_RUNOUT_PUT, "PFIFO Runout Put Address [8:3 if 512b, otherwise 12:3]"},
{ NV3_PFIFO_RUNOUT_STATUS, "PFIFO Runout Status"},
{ NV_REG_LIST_END, NULL, NULL, NULL}, // sentinel value
};
// PFIFO init code
void nv3_pfifo_init(void)
{
nv_log("Initialising PFIFO...");
nv_log("Done!\n");
}
uint32_t nv3_pfifo_read(uint32_t address)
{
// before doing anything, check the subsystem enablement state
if (!(nv3->pmc.enable >> NV3_PMC_ENABLE_PFIFO)
& NV3_PMC_ENABLE_PFIFO_ENABLED)
{
nv_log("Repressing PFIFO read. The subsystem is disabled according to pmc_enable, returning 0\n");
return 0x00;
}
uint32_t ret = 0x00;
nv_register_t* reg = nv_get_register(address, pfifo_registers, sizeof(pfifo_registers)/sizeof(pfifo_registers[0]));
// todo: friendly logging
nv_log_verbose_only("PFIFO Read from 0x%08x", address);
// if the register actually exists
if (reg)
{
// on-read function
if (reg->on_read)
ret = reg->on_read();
else
{
// Interrupt state:
// Bit 0 - Cache Error
// Bit 4 - RAMRO Triggered
// Bit 8 - RAMRO Overflow (too many invalid dma objects)
// Bit 12 - DMA Pusher
// Bit 16 - DMA Page Table Entry (pagefault?)
switch (reg->address)
{
case NV3_PFIFO_INTR:
ret = nv3->pfifo.interrupt_status;
break;
case NV3_PFIFO_INTR_EN:
ret = nv3->pfifo.interrupt_enable;
break;
case NV3_PFIFO_DELAY_0:
ret = nv3->pfifo.dma_delay_retry;
break;
// Debug
case NV3_PFIFO_DEBUG_0:
ret = nv3->pfifo.debug_0;
break;
case NV3_PFIFO_CONFIG_0:
ret = nv3->pfifo.config_0;
break;
// Some of these may need to become functions.
case NV3_PFIFO_CONFIG_RAMFC:
ret = nv3->pfifo.ramfc_config;
break;
case NV3_PFIFO_CONFIG_RAMHT:
ret = nv3->pfifo.ramht_config;
break;
case NV3_PFIFO_CONFIG_RAMRO:
ret = nv3->pfifo.ramro_config;
break;
/* These automatically trigger pulls when 1 is written */
case NV3_PFIFO_CACHE0_PULL0:
ret = nv3->pfifo.cache0_settings.pull0;
break;
case NV3_PFIFO_CACHE1_PULL0:
ret = nv3->pfifo.cache1_settings.pull0;
break;
case NV3_PFIFO_CACHE0_PULLER_CTX_STATE:
ret = (nv3->pfifo.cache0_settings.context_is_dirty) ? (1 << NV3_PFIFO_CACHE0_PULLER_CTX_STATE_DIRTY) : 0;
break;
case NV3_PFIFO_CACHE1_PULLER_CTX_STATE:
ret = (nv3->pfifo.cache0_settings.context_is_dirty) ? (1 << NV3_PFIFO_CACHE0_PULLER_CTX_STATE_DIRTY) : 0;
break;
/* Does this automatically push? */
case NV3_PFIFO_CACHE0_PUSH_ENABLED:
ret = nv3->pfifo.cache0_settings.push0;
break;
case NV3_PFIFO_CACHE1_PUSH_ENABLED:
ret = nv3->pfifo.cache1_settings.push0;
break;
case NV3_PFIFO_CACHE0_PUSH_CHANNEL_ID:
ret = nv3->pfifo.cache0_settings.channel;
break;
case NV3_PFIFO_CACHE1_PUSH_CHANNEL_ID:
ret = nv3->pfifo.cache1_settings.channel;
break;
case NV3_PFIFO_CACHE0_STATUS:
// CACHE0 has only one entry so it can only ever be empty or full
if (nv3->pfifo.cache0_settings.put_address == nv3->pfifo.cache0_settings.get_address)
ret |= 1 << NV3_PFIFO_CACHE0_STATUS_EMPTY;
else
ret |= 1 << NV3_PFIFO_CACHE0_STATUS_FULL;
break;
case NV3_PFIFO_CACHE1_STATUS:
// CACHE1 doesn't...
if (nv3->pfifo.cache1_settings.put_address == nv3->pfifo.cache1_settings.get_address)
ret |= 1 << NV3_PFIFO_CACHE1_STATUS_EMPTY;
// Check if Cache1 (0x7C bytes in size depending on gpu?) is full
// Based on how the drivers do it
if (!nv3_pfifo_cache1_num_free_spaces())
ret |= 1 << NV3_PFIFO_CACHE1_STATUS_FULL;
if (nv3->pfifo.runout_put != nv3->pfifo.runout_get)
ret |= 1 << NV3_PFIFO_CACHE1_STATUS_RANOUT;
break;
case NV3_PFIFO_CACHE0_PUT:
ret = nv3->pfifo.cache0_settings.put_address;
break;
case NV3_PFIFO_CACHE0_GET:
ret = nv3->pfifo.cache0_settings.get_address;
break;
case NV3_PFIFO_CACHE1_PUT:
ret = nv3->pfifo.cache1_settings.put_address;
break;
case NV3_PFIFO_CACHE1_GET:
ret = nv3->pfifo.cache1_settings.get_address;
break;
// Reassignment
case NV3_PFIFO_CACHE_REASSIGNMENT:
ret = nv3->pfifo.cache_reassignment & 0x01; //1bit meaningful
break;
// Cache1 exclusive stuff
// Control
case NV3_PFIFO_CACHE1_DMA_CONFIG_0:
ret = nv3->pfifo.cache1_settings.dma_state;
break;
case NV3_PFIFO_CACHE1_DMA_CONFIG_1:
ret = nv3->pfifo.cache1_settings.dma_length & (NV3_VRAM_SIZE_8MB) - 4; //MAX vram size
break;
case NV3_PFIFO_CACHE1_DMA_CONFIG_2:
ret = nv3->pfifo.cache1_settings.dma_address;
break;
case NV3_PFIFO_CACHE1_DMA_CONFIG_3:
if (nv3->nvbase.bus_generation == nv_bus_pci)
return NV3_PFIFO_CACHE1_DMA_CONFIG_3_TARGET_NODE_PCI;
else
return NV3_PFIFO_CACHE1_DMA_CONFIG_3_TARGET_NODE_AGP;
break;
case NV3_PFIFO_CACHE1_DMA_STATUS:
ret = nv3->pfifo.cache1_settings.dma_status;
break;
case NV3_PFIFO_CACHE1_DMA_TLB_PT_BASE:
ret = nv3->pfifo.cache1_settings.dma_tlb_pt_base;
break;
case NV3_PFIFO_CACHE1_DMA_TLB_PTE:
ret = nv3->pfifo.cache1_settings.dma_tlb_pte;
break;
case NV3_PFIFO_CACHE1_DMA_TLB_TAG:
ret = nv3->pfifo.cache1_settings.dma_tlb_tag;
break;
// Runout
case NV3_PFIFO_RUNOUT_GET:
ret = nv3->pfifo.runout_get;
break;
case NV3_PFIFO_RUNOUT_PUT:
ret = nv3->pfifo.runout_put;
break;
case NV3_PFIFO_RUNOUT_STATUS:
if (nv3->pfifo.runout_put == nv3->pfifo.runout_get)
ret |= 1 << NV3_PFIFO_RUNOUT_STATUS_EMPTY; /* good news */
else
ret |= 1 << NV3_PFIFO_RUNOUT_STATUS_RANOUT; /* bad news */
/* TODO: the following code sucks (move to a functio?) */
uint32_t new_size_ramro = ((nv3->pfifo.ramro_config >> NV3_PFIFO_CONFIG_RAMRO_SIZE) & 0x01);
if (new_size_ramro == 0)
new_size_ramro = 0x200;
else if (new_size_ramro == 1)
new_size_ramro = 0x2000;
// WTF?
if (nv3->pfifo.runout_put + 0x08 & (new_size_ramro - 0x08) == nv3->pfifo.runout_get)
ret |= 1 << NV3_PFIFO_RUNOUT_STATUS_FULL; /* VERY BAD news */
break;
/* Cache1 is handled below - cache0 only has one entry */
case NV3_PFIFO_CACHE0_CTX:
ret = nv3->pfifo.cache0_settings.context[0];
break;
}
}
if (reg->friendly_name)
nv_log_verbose_only(": 0x%08x <- %s\n", ret, reg->friendly_name);
else
nv_log_verbose_only("\n");
}
/* Handle some special memory areas */
else if (address >= NV3_PFIFO_CACHE1_CTX_START && address <= NV3_PFIFO_CACHE1_CTX_END)
{
uint32_t ctx_entry_id = ((address - NV3_PFIFO_CACHE1_CTX_START) / 16) % 8;
ret = nv3->pfifo.cache1_settings.context[ctx_entry_id];
nv_log_verbose_only("PFIFO Cache1 CTX Read Entry=%d Value=0x%04x\n", ctx_entry_id, ret);
}
/* Direct cache read stuff */
else if (address >= NV3_PFIFO_CACHE0_METHOD_START && address <= NV3_PFIFO_CACHE0_METHOD_END)
{
nv_log_verbose_only("PFIFO Cache0 Read\n");
// See if we want the object name or the channel/subchannel information.
if (address & 4)
{
nv_log_verbose_only("Data=0x%08x\n", nv3->pfifo.cache0_entry.data);
return nv3->pfifo.cache0_entry.data;
}
else
{
uint32_t final = nv3->pfifo.cache0_entry.method | (nv3->pfifo.cache0_entry.subchannel << NV3_PFIFO_CACHE1_METHOD_SUBCHANNEL);
nv_log_verbose_only("Param (subchannel=15:13, method=12:2)=0x%08x\n", final);
return final;
}
}
else if (address >= NV3_PFIFO_CACHE1_METHOD_START && address <= NV3_PFIFO_CACHE1_METHOD_END)
{
// Not sure if REV C changes this. It should...
uint32_t slot = 0;
// shift right by 3, convert from address, to slot.
if (nv3->nvbase.gpu_revision == NV3_PCI_CFG_REVISION_C00)
slot = (address >> 3) & 0x3F;
else
slot = (address >> 3) & 0x1F;
nv_log_verbose_only("PFIFO Cache1 Read slot=%d", slot);
// See if we want the object name or the channel/subchannel information.
if (address & 4)
{
nv_log_verbose_only("Data=0x%08x\n", nv3->pfifo.cache1_entries[slot].data);
return nv3->pfifo.cache1_entries[slot].data;
}
else
{
uint32_t final = nv3->pfifo.cache1_entries[slot].method | (nv3->pfifo.cache1_entries[slot].subchannel << NV3_PFIFO_CACHE1_METHOD_SUBCHANNEL);
nv_log_verbose_only("Param (subchannel=15:13, method=12:2)=0x%08x\n", final);
return final;
}
}
else
{
nv_log(": Unknown register read (address=0x%08x), returning 0x00\n", address);
}
return ret;
}
void nv3_pfifo_trigger_dma_if_required(void)
{
// Not a thing for cache0
bool cache1_dma = false;
/* Check that DMA is enabled */
if ((nv3->pfifo.cache1_settings.dma_state & NV3_PFIFO_CACHE1_DMA_STATUS_STATE_RUNNING)
&& nv3->pfifo.cache1_settings.dma_enabled)
{
uint32_t bytes_to_send = nv3->pfifo.cache1_settings.dma_length;
uint32_t where_to_send = nv3->pfifo.cache1_settings.dma_address;
uint32_t target_node = nv3->pfifo.cache1_settings.dma_target_node; //2=pci, 3=agp.
/* Pagetable information */
uint32_t tlb_pt_base = nv3->pfifo.cache1_settings.dma_tlb_pt_base;
uint32_t tlb_pt_entry = nv3->pfifo.cache1_settings.dma_tlb_pte; // notify_obj_page
uint32_t tlb_pt_tag = nv3->pfifo.cache1_settings.dma_tlb_tag; // 0xFFFFFFFF usually?
/*
going to treat the format the same as notifiers
*/
if (!(tlb_pt_entry & NV3_PFIFO_CACHE1_DMA_TLB_PTE_IS_PRESENT))
{
warning("NV3: Tried to DMA to a non-existent page! Big Problem!");
return;
}
uint32_t final_page_base = tlb_pt_entry & 0xFFFFF000; /* pull out 31:12 */
/*
page size is 0x1000
*/
uint32_t final_address = final_page_base + (tlb_pt_entry << 10) + where_to_send; //x86 page size is 0x1000 (maybe rsh where_to_send by 2)
nv_log_verbose_only("DMA Engine: DMA to %08x length=%08x", final_address, bytes_to_send);
//-dma_bm_write()
}
//we're done
nv3->pfifo.cache1_settings.dma_state &= ~NV3_PFIFO_CACHE1_DMA_STATUS_STATE_RUNNING;
}
void nv3_pfifo_write(uint32_t address, uint32_t val)
{
// before doing anything, check the subsystem enablement
if (!(nv3->pmc.enable >> NV3_PMC_ENABLE_PFIFO)
& NV3_PMC_ENABLE_PFIFO_ENABLED)
{
nv_log("Repressing PFIFO write. The subsystem is disabled according to pmc_enable\n");
return;
}
nv_register_t* reg = nv_get_register(address, pfifo_registers, sizeof(pfifo_registers)/sizeof(pfifo_registers[0]));
nv_log_verbose_only("PFIFO Write 0x%08x -> 0x%08x", val, address);
// if the register actually exists
if (reg)
{
// on-read function
if (reg->on_write)
reg->on_write(val);
else
{
switch (reg->address)
{
// Interrupt state:
// Bit 0 - Cache Error
// Bit 4 - RAMRO Triggered
// Bit 8 - RAMRO Overflow (too many invalid dma objects)
// Bit 12 - DMA Pusher
// Bit 16 - DMA Page Table Entry (pagefault?)
case NV3_PFIFO_INTR:
nv3->pfifo.interrupt_status &= ~val;
nv3_pmc_clear_interrupts();
// update the internal cache error state
if (!nv3->pfifo.interrupt_status & NV3_PFIFO_INTR_CACHE_ERROR)
nv3->pfifo.debug_0 &= ~NV3_PFIFO_INTR_CACHE_ERROR;
break;
case NV3_PFIFO_INTR_EN:
nv3->pfifo.interrupt_enable = val & 0x00011111;
nv3_pmc_handle_interrupts(true);
break;
case NV3_PFIFO_DELAY_0:
nv3->pfifo.dma_delay_retry = val;
break;
case NV3_PFIFO_CONFIG_0:
nv3->pfifo.config_0 = val;
break;
case NV3_PFIFO_CONFIG_RAMHT:
nv3->pfifo.ramht_config = val;
// This code sucks a bit fix it later
#ifdef ENABLE_NV_LOG
uint32_t new_size_ramht = ((val >> 16) & 0x03);
if (new_size_ramht == 0)
new_size_ramht = 0x1000;
else if (new_size_ramht == 1)
new_size_ramht = 0x2000;
else if (new_size_ramht == 2)
new_size_ramht = 0x4000;
else if (new_size_ramht == 3)
new_size_ramht = 0x8000;
nv_log("RAMHT Reconfiguration\n"
"Base Address in RAMIN: %d\n"
"Size: 0x%08x bytes\n", ((nv3->pfifo.ramht_config >> NV3_PFIFO_CONFIG_RAMHT_BASE_ADDRESS) & 0x0F) << 12, new_size_ramht);
#endif
break;
case NV3_PFIFO_CONFIG_RAMFC:
nv3->pfifo.ramfc_config = val;
nv_log("RAMFC Reconfiguration\n"
"Base Address in RAMIN: %d\n", ((nv3->pfifo.ramfc_config >> NV3_PFIFO_CONFIG_RAMFC_BASE_ADDRESS) & 0x7F) << 9);
break;
case NV3_PFIFO_CONFIG_RAMRO:
nv3->pfifo.ramro_config = val;
uint32_t new_size_ramro = ((val >> NV3_PFIFO_CONFIG_RAMRO_SIZE) & 0x01);
if (new_size_ramro == 0)
new_size_ramro = 0x200;
else if (new_size_ramro == 1)
new_size_ramro = 0x2000;
nv_log("RAMRO Reconfiguration\n"
"Base Address in RAMIN: %d\n"
"Size: 0x%08x bytes\n", ((nv3->pfifo.ramro_config >> NV3_PFIFO_CONFIG_RAMRO_BASE_ADDRESS) & 0x7F) << 9, new_size_ramro);
break;
case NV3_PFIFO_DEBUG_0:
nv3->pfifo.debug_0 = val;
break;
// Reassignment
case NV3_PFIFO_CACHE_REASSIGNMENT:
nv3->pfifo.cache_reassignment = val & 0x01; //1bit meaningful
break;
// Control - these can trigger pulls
case NV3_PFIFO_CACHE0_PULL0:
nv3->pfifo.cache0_settings.pull0 = val; // 8bits meaningful
if (nv3->pfifo.cache0_settings.pull0 & (1 >> NV3_PFIFO_CACHE0_PULL0_ENABLED))
nv3_pfifo_cache0_pull();
break;
case NV3_PFIFO_CACHE1_PULL0:
nv3->pfifo.cache1_settings.pull0 = val; // 8bits meaningful
if (nv3->pfifo.cache1_settings.pull0 & (1 >> NV3_PFIFO_CACHE1_PULL0_ENABLED))
nv3_pfifo_cache1_pull();
break;
case NV3_PFIFO_CACHE0_PULLER_CTX_STATE:
nv3->pfifo.cache0_settings.context_is_dirty = (val >> NV3_PFIFO_CACHE0_PULLER_CTX_STATE_DIRTY) & 0x01;
break;
case NV3_PFIFO_CACHE1_PULLER_CTX_STATE:
nv3->pfifo.cache1_settings.context_is_dirty = (val >> NV3_PFIFO_CACHE0_PULLER_CTX_STATE_DIRTY) & 0x01;
break;
case NV3_PFIFO_CACHE0_PUSH_ENABLED:
nv3->pfifo.cache0_settings.push0 = val;
break;
case NV3_PFIFO_CACHE1_PUSH_ENABLED:
nv3->pfifo.cache1_settings.push0 = val;
break;
case NV3_PFIFO_CACHE0_PUSH_CHANNEL_ID:
nv3->pfifo.cache0_settings.channel = val;
break;
case NV3_PFIFO_CACHE1_PUSH_CHANNEL_ID:
nv3->pfifo.cache1_settings.channel = val;
break;
// CACHE0_STATUS and CACHE1_STATUS are not writable
// DMA configuration
case NV3_PFIFO_CACHE1_DMA_CONFIG_0:
nv3->pfifo.cache1_settings.dma_state = val;
break;
case NV3_PFIFO_CACHE1_DMA_CONFIG_1:
nv3->pfifo.cache1_settings.dma_length = val;
break;
case NV3_PFIFO_CACHE1_DMA_CONFIG_2:
nv3->pfifo.cache1_settings.dma_address = val;
break;
case NV3_PFIFO_CACHE1_DMA_STATUS:
nv3->pfifo.cache1_settings.dma_status = val;
break;
case NV3_PFIFO_CACHE1_DMA_TLB_PT_BASE:
nv3->pfifo.cache1_settings.dma_tlb_pt_base = val;
break;
case NV3_PFIFO_CACHE1_DMA_TLB_PTE:
nv3->pfifo.cache1_settings.dma_tlb_pte = val;
break;
case NV3_PFIFO_CACHE1_DMA_TLB_TAG:
nv3->pfifo.cache1_settings.dma_tlb_tag = val;
break;
/* Put and Get addresses */
case NV3_PFIFO_CACHE0_PUT:
nv3->pfifo.cache0_settings.put_address = val;
break;
case NV3_PFIFO_CACHE0_GET:
nv3->pfifo.cache0_settings.get_address = val;
break;
case NV3_PFIFO_CACHE1_PUT:
nv3->pfifo.cache1_settings.put_address = val;
break;
case NV3_PFIFO_CACHE1_GET:
nv3->pfifo.cache1_settings.get_address = val;
break;
case NV3_PFIFO_RUNOUT_GET:
{
uint32_t size_get = ((nv3->pfifo.ramro_config >> NV3_PFIFO_CONFIG_RAMRO_SIZE) & 0x01);
if (size_get == 0) //512b
nv3->pfifo.runout_get = val & (NV3_RAMIN_RAMRO_SIZE_0 - 0x07);
else
nv3->pfifo.runout_get = val & (NV3_RAMIN_RAMRO_SIZE_1 - 0x07);
break;
}
case NV3_PFIFO_RUNOUT_PUT:
{
uint32_t size_put = ((nv3->pfifo.ramro_config >> NV3_PFIFO_CONFIG_RAMRO_SIZE) & 0x01);
if (size_put == 0) //512b
nv3->pfifo.runout_put = val & (NV3_RAMIN_RAMRO_SIZE_0 - 0x07);
else
nv3->pfifo.runout_put = val & (NV3_RAMIN_RAMRO_SIZE_1 - 0x07);
break;
}
/* Cache1 Context is handled below */
case NV3_PFIFO_CACHE0_CTX:
nv3->pfifo.cache0_settings.context[0] = val;
break;
}
}
if (reg->friendly_name)
nv_log_verbose_only(": %s\n", reg->friendly_name);
else
nv_log_verbose_only("\n");
}
else if (address >= NV3_PFIFO_CACHE0_METHOD_START && address <= NV3_PFIFO_CACHE0_METHOD_END)
{
nv_log_verbose_only("PFIFO Cache0 Write\n");
// 3104 always written after 3100
if (address & 4)
{
nv_log_verbose_only("Name = 0x%08x\n", val);
nv3->pfifo.cache0_entry.data = val;
nv3_pfifo_cache0_pull(); // immediately pull out
}
else
{
nv3->pfifo.cache0_entry.method = (val & 0x1FFC);
nv3->pfifo.cache0_entry.subchannel = (val >> NV3_PFIFO_CACHE1_METHOD_SUBCHANNEL) & 0x07;
nv_log_verbose_only("Subchannel = 0x%08x, method = 0x%04x\n", nv3->pfifo.cache0_entry.subchannel, nv3->pfifo.cache0_entry.method);
}
}
else if (address >= NV3_PFIFO_CACHE1_METHOD_START && address <= NV3_PFIFO_CACHE1_METHOD_END)
{
// Not sure if REV C changes this. It should...
uint32_t slot = 0;
if (nv3->nvbase.gpu_revision == NV3_PCI_CFG_REVISION_C00)
slot = (address >> 3) & 0x3F;
else
slot = (address >> 3) & 0x1F;
uint32_t real_entry = nv3_pfifo_cache1_normal2gray(slot);
nv_log_verbose_only("Cache1 Write Slot %d (Gray code)", real_entry);
// See if we want the object name or the channel/subchannel information.
if (address & 4)
{
nv_log_verbose_only("Name = 0x%08x\n", val);
nv3->pfifo.cache1_entries[real_entry].data = val;
}
else
{
nv3->pfifo.cache1_entries[real_entry].method = (val & 0x1FFC);
nv3->pfifo.cache1_entries[real_entry].subchannel = (val >> NV3_PFIFO_CACHE1_METHOD_SUBCHANNEL) & 0x07;
nv_log_verbose_only("Subchannel = 0x%08x, method = 0x%04x\n", nv3->pfifo.cache1_entries[real_entry].subchannel, nv3->pfifo.cache1_entries[real_entry].method);
}
}
/* Handle some special memory areas */
else if (address >= NV3_PFIFO_CACHE1_CTX_START && address <= NV3_PFIFO_CACHE1_CTX_END)
{
uint32_t ctx_entry_id = ((address - NV3_PFIFO_CACHE1_CTX_START) / 16) % 8;
nv3->pfifo.cache1_settings.context[ctx_entry_id] = val;
nv_log_verbose_only("PFIFO Cache1 CTX Write Entry=%d value=0x%04x\n", ctx_entry_id, val);
}
else /* Completely unknown */
{
nv_log(": Unknown register write (address=0x%08x)\n", address);
}
/* Trigger DMA for notifications if we need to */
nv3_pfifo_trigger_dma_if_required();
}
/*
https://en.wikipedia.org/wiki/Gray_code
WHY?????? IT'S NOT A TELEGRAPH IT'S A GPU?????
Convert from a normal number to a total insanity number which is only used in PFIFO CACHE1 for ungodly and totally unknowable reasons
(Possibly it just makes it easier to implement in logic)
I decided to use a lookup table to save everyone's time, also the numbers generated from the function
that existed here before didn't make any sense
*/
#define NV3_GRAY_TABLE_NUM_ENTRIES 64
uint8_t nv3_pfifo_cache1_gray_code_table[NV3_GRAY_TABLE_NUM_ENTRIES] = {
0b000000, 0b000001, 0b000011, 0b000010, 0b000110, 0b000111, 0b000101, 0b000100, //0x07
0b001100, 0b001101, 0b001111, 0b001110, 0b001010, 0b001011, 0b001001, 0b001000, //0x0F
0b011000, 0b011001, 0b011011, 0b011010, 0b011110, 0b011111, 0b011101, 0b011100, //0x17
0b010100, 0b010101, 0b010111, 0b010110, 0b010010, 0b010011, 0b010001, 0b010000, //0x1F
0b110000, 0b110001, 0b110011, 0b110010, 0b110110, 0b110111, 0b110101, 0b110100, //0x27
0b111100, 0b111101, 0b111111, 0b111110, 0b111010, 0b111011, 0b111001, 0b111000, //0x2F
0b101000, 0b101001, 0b101011, 0b101010, 0b101110, 0b101111, 0b101101, 0b101100, //0x37
0b100100, 0b100101, 0b100111, 0b100110, 0b100010, 0b100011, 0b100001, 0b100000 //0x3F
};
/* The function is called up to hundreds of thousands of times per second, it's too slow to do anything else */
uint8_t nv3_pfifo_cache1_binary_code_table[NV3_GRAY_TABLE_NUM_ENTRIES] =
{
0x00, 0x01, 0x03, 0x02, 0x07, 0x06, 0x04, 0x05, // 0x07 (0)
0x0F, 0x0E, 0x0C, 0x0D, 0x08, 0x09, 0x0B, 0x0A, // 0x0F (1000)
0x1F, 0x1E, 0x1C, 0x1D, 0x18, 0x19, 0x1B, 0x1A, // 0x17 (10000)
0x10, 0x11, 0x13, 0x12, 0x17, 0x16, 0x14, 0x15, // 0x1F (11000)
0x3F, 0x3E, 0x3C, 0x3D, 0x38, 0x39, 0x3B, 0x3A, // 0x27 (100000)
0x30, 0x31, 0x33, 0x32, 0x37, 0x36, 0x34, 0x35, // 0x2F (101000)
0x20, 0x21, 0x23, 0x22, 0x27, 0x26, 0x24, 0x25, // 0x37 (110000)
0x2F, 0x2E, 0x2C, 0x2D, 0x28, 0x29, 0x2B, 0x2A, // 0X3f (111000)
};
uint32_t nv3_pfifo_cache1_normal2gray(uint32_t val)
{
return nv3_pfifo_cache1_gray_code_table[val];
}
/*
Back to sanity
*/
uint32_t nv3_pfifo_cache1_gray2normal(uint32_t val)
{
return nv3_pfifo_cache1_binary_code_table[val];
}
/*
You can't push into cache0 on the real hardware, but it's not practically done because Cache0 is meant to be reserved for software objects,
NV_USER writes always go to CACHE1
*/
// Pulls graphics objects OUT of cache0
void nv3_pfifo_cache0_pull(void)
{
// Do nothing if PFIFO CACHE0 is disabled
if (!nv3->pfifo.cache0_settings.pull0 & (1 >> NV3_PFIFO_CACHE0_PULL0_ENABLED))
return;
// Do nothing if there is nothing in cache0 to pull
if (nv3->pfifo.cache0_settings.put_address == nv3->pfifo.cache0_settings.get_address)
return;
// There is only one entry for cache0
uint8_t current_channel = nv3->pfifo.cache0_settings.channel;
uint8_t current_subchannel = nv3->pfifo.cache0_entry.subchannel;
uint32_t current_param = nv3->pfifo.cache0_entry.data;
uint16_t current_method = nv3->pfifo.cache0_entry.method;
// i.e. there is no method in cache0, so we have to find the object.
if (!current_method)
{
// flip the get address over
nv3->pfifo.cache0_settings.get_address ^= 0x04;
if (!nv3_ramin_find_object(current_param, 0, current_channel, current_subchannel))
return; // interrupt was fired, and we went to ramro
}
uint32_t current_context = nv3->pfifo.cache0_settings.context[0]; // only 1 entry for CACHE0 so basically ignore the other context entries?
uint8_t class_id = ((nv3_ramin_context_t*)&current_context)->class_id;
// Tell the CPU if we found a software method and turn off cache pulling
if (!(current_context & 0x800000))
{
nv_log_verbose_only("The object in CACHE0 is a software object\n");
nv3->pfifo.cache0_settings.pull0 |= NV3_PFIFO_CACHE0_PULL0_SOFTWARE_METHOD;
nv3->pfifo.cache0_settings.pull0 &= ~NV3_PFIFO_CACHE0_PULL0_ENABLED;
nv3_pfifo_interrupt(NV3_PFIFO_INTR_CACHE_ERROR, true);
return;
}
// Is this needed?
nv3->pfifo.cache0_settings.get_address ^= 0x04;
#ifndef RELEASE_BUILD
nv_log_verbose_only("***** DEBUG: CACHE0 PULLED ****** Contextual information below\n");
nv3_ramin_context_t context_structure = *(nv3_ramin_context_t*)&current_context;
nv3_debug_ramin_print_context_info(current_param, context_structure);
nv3_pgraph_submit(current_param, current_method, current_channel, current_subchannel, class_id & 0x1F, context_structure);
#endif
}
void nv3_pfifo_context_switch(uint32_t new_channel)
{
/* Send our contexts to RAMFC. Load the new ones from RAMFC. */
if (new_channel >= NV3_DMA_CHANNELS)
fatal("nv3_pfifo_context_switch: Tried to switch to invalid dma channel");
//uint16_t ramfc_base = nv3->pfifo.ramfc_config >> NV3_PFIFO_CONFIG_RAMFC_BASE_ADDRESS & 0xF;
}
// NV_USER writes go here!
// Pushes graphics objects into cache1
void nv3_pfifo_cache1_push(uint32_t addr, uint32_t param)
{
bool oh_shit = false; // RAMRO needed
nv3_ramin_ramro_reason oh_shit_reason = 0x00; // It's all good for now
// bit 23 of a ramin dword means it's a write...
uint32_t new_address = 0;
uint32_t method_offset = (addr & 0x1FFC); // size of dma object is 0x2000 and some universal methods are implemented at this point, like free
// Up to 128 per envytools?
uint32_t channel = (addr >> NV3_OBJECT_SUBMIT_CHANNEL) & 0x7F;
uint32_t subchannel = (addr >> NV3_OBJECT_SUBMIT_SUBCHANNEL) & (NV3_DMA_CHANNELS - 1);
// first make sure there is even any cache available
if (!nv3->pfifo.cache1_settings.push0)
{
oh_shit = true;
oh_shit_reason = nv3_runout_reason_no_cache_available;
new_address |= (nv3_runout_reason_no_cache_available << NV3_PFIFO_RUNOUT_RAMIN_ERR);
}
// Check if runout is full
if (nv3->pfifo.runout_get != nv3->pfifo.runout_put)
{
oh_shit = true;
oh_shit_reason = nv3_runout_reason_cache_ran_out; // ? really ? I guess this means we already ran out..
new_address |= (nv3_runout_reason_cache_ran_out << NV3_PFIFO_RUNOUT_RAMIN_ERR);
}
if (!nv3_pfifo_cache1_num_free_spaces())
{
oh_shit = true;
oh_shit_reason = nv3_runout_reason_free_count_overrun;
new_address |= (nv3_runout_reason_free_count_overrun << NV3_PFIFO_RUNOUT_RAMIN_ERR);
}
// 0x0 is used for creating the object.
if (method_offset > 0 && method_offset < 0x100)
{
// Reserved nvidia methods
oh_shit = true;
oh_shit_reason = nv3_runout_reason_reserved_access;
new_address |= (nv3_runout_reason_reserved_access << NV3_PFIFO_RUNOUT_RAMIN_ERR);
}
// Now check for context switching
if (channel != nv3->pfifo.cache1_settings.channel)
{
// Cache reassignment required
if (!nv3->pfifo.cache_reassignment
|| (nv3->pfifo.cache1_settings.get_address != nv3->pfifo.cache1_settings.put_address))
{
oh_shit = true;
oh_shit_reason = nv3_runout_reason_no_cache_available;
new_address |= (nv3_runout_reason_no_cache_available << NV3_PFIFO_RUNOUT_RAMIN_ERR);
}
nv3_pfifo_context_switch(channel);
}
// Did we fuck up?
if (oh_shit)
{
nv_log("OH CRAP: Runout Error=%d Channel=%d Subchannel=%d Method=0x%04x",
oh_shit_reason, channel, subchannel, method_offset);
nv3_ramro_write(nv3->pfifo.runout_put, new_address);
nv3_ramro_write(nv3->pfifo.runout_put + 4, param);
nv3->pfifo.runout_put += 0x08;
uint32_t ramro_size = (nv3->pfifo.ramro_config >> NV3_PFIFO_CONFIG_RAMRO_SIZE) & 0x01;
/* Make sure it's valid */
switch (ramro_size)
{
case 0:
nv3->pfifo.runout_put &= (NV3_RAMIN_RAMRO_SIZE_0 - 0x07);
break;
case 1:
nv3->pfifo.runout_put &= (NV3_RAMIN_RAMRO_SIZE_1 - 0x07);
break;
}
//Fire the interrupt. Also the very bad interrupt...
if (nv3->pfifo.runout_get == nv3->pfifo.runout_put)
nv3_pfifo_interrupt(NV3_PFIFO_INTR_RUNOUT_OVERFLOW, true);
else
nv3_pfifo_interrupt(NV3_PFIFO_INTR_RUNOUT, true);
return;
}
// We didn't. Let's put it in CACHE1
uint32_t current_put_index = nv3->pfifo.cache1_settings.put_address >> 2;
nv3->pfifo.cache1_entries[current_put_index].subchannel = subchannel;
nv3->pfifo.cache1_entries[current_put_index].method = method_offset;
nv3->pfifo.cache1_entries[current_put_index].data = param;
// now we have to recalculate the cache1 put address
uint32_t next_put_address = nv3_pfifo_cache1_gray2normal(current_put_index);
next_put_address++;
if (nv3->nvbase.gpu_revision >= NV3_PCI_CFG_REVISION_C00) // RIVA 128ZX#
next_put_address &= (NV3_PFIFO_CACHE1_SIZE_REV_C - 1);
else
next_put_address &= (NV3_PFIFO_CACHE1_SIZE_REV_AB - 1);
nv3->pfifo.cache1_settings.put_address = nv3_pfifo_cache1_normal2gray(next_put_address) << 2;
nv_log_verbose_only("Submitted object [PIO]: Channel %d.%d, Parameter 0x%08x, Method ID 0x%04x (Put Address is now %d)\n",
channel, subchannel, param, method_offset, nv3->pfifo.cache1_settings.put_address);
// Now we're done. Phew!
}
// Pulls graphics objects OUT of cache1
void nv3_pfifo_cache1_pull(void)
{
// Do nothing if PFIFO CACHE1 is disabled
if (!nv3->pfifo.cache1_settings.pull0 & (1 >> NV3_PFIFO_CACHE1_PULL0_ENABLED))
return;
// Do nothing if there is nothing in cache1 to pull
if (nv3->pfifo.cache1_settings.put_address == nv3->pfifo.cache1_settings.get_address)
return;
uint32_t get_index = nv3->pfifo.cache1_settings.get_address >> 2; // 32 bit aligned probably
uint8_t current_channel = nv3->pfifo.cache1_settings.channel;
uint8_t current_subchannel = nv3->pfifo.cache1_entries[get_index].subchannel;
uint32_t current_param = nv3->pfifo.cache1_entries[get_index].data;
uint16_t current_method = nv3->pfifo.cache1_entries[get_index].method;
// NV_ROOT
if (!current_method)
{
if (!nv3_ramin_find_object(current_param, 1, current_channel, current_subchannel))
return; // interrupt was fired, and we went to ramro
}
// should this be obtained from the grobj? Test on real nv3 h/w after drawrect.nvp works
uint32_t current_context = nv3->pfifo.cache1_settings.context[current_subchannel]; // get the current subchannel
uint8_t class_id = ((nv3_ramin_context_t*)&current_context)->class_id;
// start by incrementing
uint32_t next_get_address = nv3_pfifo_cache1_gray2normal(get_index) + 1;
if (nv3->nvbase.gpu_revision >= NV3_PCI_CFG_REVISION_C00) // RIVA 128ZX
next_get_address &= (NV3_PFIFO_CACHE1_SIZE_REV_C - 1);
else
next_get_address &= (NV3_PFIFO_CACHE1_SIZE_REV_AB - 1);
// Tell the CPU if we found a software method
//bit23 unset=software
//bit23 set=hardware
if (!(current_context & 0x800000))
{
nv_log_verbose_only("The object in CACHE1 is a software object\n");
nv3->pfifo.cache1_settings.pull0 |= NV3_PFIFO_CACHE0_PULL0_SOFTWARE_METHOD;
nv3->pfifo.cache1_settings.pull0 &= ~NV3_PFIFO_CACHE0_PULL0_ENABLED;
nv3_pfifo_interrupt(NV3_PFIFO_INTR_CACHE_ERROR, true);
return;
}
// Is this needed?
nv3->pfifo.cache1_settings.get_address = nv3_pfifo_cache1_normal2gray(next_get_address) << 2;
#ifndef RELEASE_BUILD
nv_log_verbose_only("***** DEBUG: CACHE1 PULLED ****** Contextual information below\n");
nv3_ramin_context_t context_structure = *(nv3_ramin_context_t*)&current_context;
nv3_debug_ramin_print_context_info(current_param, context_structure);
#endif
nv3_pgraph_submit(current_param, current_method, current_channel, current_subchannel, class_id & 0x1F, context_structure);
//Todo: finish it
}
// THIS IS PER SUBCHANNEL!
uint32_t nv3_pfifo_cache1_num_free_spaces(void)
{
// get the index
uint32_t get_index = nv3->pfifo.cache1_settings.get_address >> 2;
uint32_t put_index = nv3->pfifo.cache1_settings.put_address >> 2;
uint32_t real_get_address = nv3_pfifo_cache1_gray2normal(get_index) << 2;
uint32_t real_put_address = nv3_pfifo_cache1_gray2normal(put_index) << 2;
// There is no hope of being able to understand it. Nobody can understand
return (real_get_address - real_put_address - 4) & 0x7C; // there are 64 entries what
}

View File

@@ -0,0 +1,632 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* NV3 PGRAPH (Scene Graph for 2D/3D Accelerated Graphics)
*
*
*
* Authors: Connor Hyde, <mario64crashed@gmail.com> I need a better email address ;^)
*
* Copyright 2024-2025 starfrost
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/pci.h>
#include <86box/rom.h> // DEPENDENT!!!
#include <86box/video.h>
#include <86box/nv/vid_nv.h>
#include <86box/nv/vid_nv3.h>
#include <86box/nv/classes/vid_nv3_classes.h>
// Initialise the PGRAPH subsystem.
void nv3_pgraph_init(void)
{
nv_log("Initialising PGRAPH...");
// Set up the vblank interrupt
nv3->nvbase.svga.vblank_start = nv3_pgraph_vblank_start;
nv_log("Done!\n");
}
//
// ****** PGRAPH register list START ******
//
nv_register_t pgraph_registers[] = {
{ NV3_PGRAPH_DEBUG_0, "PGRAPH Debug 0", NULL, NULL },
{ NV3_PGRAPH_DEBUG_1, "PGRAPH Debug 1", NULL, NULL },
{ NV3_PGRAPH_DEBUG_2, "PGRAPH Debug 2", NULL, NULL },
{ NV3_PGRAPH_DEBUG_3, "PGRAPH Debug 3", NULL, NULL },
{ NV3_PGRAPH_INTR_0, "PGRAPH Interrupt Status 0", NULL, NULL },
{ NV3_PGRAPH_INTR_EN_0, "PGRAPH Interrupt Enable 0", NULL, NULL },
{ NV3_PGRAPH_INTR_1, "PGRAPH Interrupt Status 1", NULL, NULL },
{ NV3_PGRAPH_INTR_EN_1, "PGRAPH Interrupt Enable 1", NULL, NULL },
{ NV3_PGRAPH_CTX_SWITCH, "PGRAPH DMA Context Switch", NULL, NULL },
{ NV3_PGRAPH_CONTEXT_CONTROL, "PGRAPH DMA Context Control", NULL, NULL },
{ NV3_PGRAPH_CONTEXT_USER, "PGRAPH DMA Context User", NULL, NULL },
//{ NV3_PGRAPH_CONTEXT_CACHE(0), "PGRAPH DMA Context Cache", NULL, NULL },
{ NV3_PGRAPH_ABS_UCLIP_XMIN, "PGRAPH Absolute Clip Minimum X [17:0]", NULL, NULL },
{ NV3_PGRAPH_ABS_UCLIP_XMAX, "PGRAPH Absolute Clip Maximum X [17:0]", NULL, NULL },
{ NV3_PGRAPH_ABS_UCLIP_YMIN, "PGRAPH Absolute Clip Minimum Y [17:0]", NULL, NULL },
{ NV3_PGRAPH_ABS_UCLIP_YMAX, "PGRAPH Absolute Clip Maximum Y [17:0]", NULL, NULL },
{ NV3_PGRAPH_SRC_CANVAS_MIN, "PGRAPH Source Canvas Minimum Coordinates (Bits 30:16 = Y, Bits 10:0 = X)", NULL, NULL},
{ NV3_PGRAPH_SRC_CANVAS_MAX, "PGRAPH Source Canvas Maximum Coordinates (Bits 30:16 = Y, Bits 10:0 = X)", NULL, NULL},
{ NV3_PGRAPH_DST_CANVAS_MIN, "PGRAPH Destination Canvas Minimum Coordinates (Bits 30:16 = Y, Bits 10:0 = X)", NULL, NULL},
{ NV3_PGRAPH_DST_CANVAS_MAX, "PGRAPH Destination Canvas Maximum Coordinates (Bits 30:16 = Y, Bits 10:0 = X)", NULL, NULL},
{ NV3_PGRAPH_PATTERN_COLOR_0_RGB, "PGRAPH Pattern Color 0_0 (Bits 29:20 = Red, Bits 19:10 = Green, Bits 9:0 = Blue)", NULL, NULL, },
{ NV3_PGRAPH_PATTERN_COLOR_0_ALPHA, "PGRAPH Pattern Color 0_1 (Bits 7:0 = Alpha)", NULL, NULL, },
{ NV3_PGRAPH_PATTERN_COLOR_1_RGB, "PGRAPH Pattern Color 1_0 (Bits 29:20 = Red, Bits 19:10 = Green, Bits 9:0 = Blue)", NULL, NULL, },
{ NV3_PGRAPH_PATTERN_COLOR_1_ALPHA, "PGRAPH Pattern Color 1_1 (Bits 7:0 = Alpha)", NULL, NULL, },
{ NV3_PGRAPH_PATTERN_BITMAP_HIGH, "PGRAPH Pattern Bitmap (High 32bits)", NULL, NULL},
{ NV3_PGRAPH_PATTERN_BITMAP_LOW, "PGRAPH Pattern Bitmap (Low 32bits)", NULL, NULL},
{ NV3_PGRAPH_PATTERN_SHAPE, "PGRAPH Pattern Shape (1:0 - 0=8x8, 1=64x1, 2=1x64)", NULL, NULL},
{ NV3_PGRAPH_ROP3, "PGRAPH GDI Ternary Render Operation ROP3 (2^3 bits = 256 possible operations)", NULL, NULL},
{ NV3_PGRAPH_PLANE_MASK, "PGRAPH Current Plane Mask (7:0)", NULL, NULL},
{ NV3_PGRAPH_CHROMA_KEY, "PGRAPH Chroma Key (17:0) (Bit 30 = Alpha, 29:20 = Red, 19:10 = Green, 9:0 = Blue)", NULL, NULL},
{ NV3_PGRAPH_BETA, "PGRAPH Beta factor", NULL, NULL },
{ NV3_PGRAPH_DMA, "PGRAPH DMA", NULL, NULL },
{ NV3_PGRAPH_CLIP_MISC, "PGRAPH Clipping Miscellaneous Settings", NULL, NULL },
{ NV3_PGRAPH_NOTIFY, "PGRAPH Notifier (Wip...)", NULL, NULL },
{ NV3_PGRAPH_CLIP0_MIN, "PGRAPH Clip0 Min (Bits 30:16 = Y, Bits 10:0 = X)", NULL, NULL},
{ NV3_PGRAPH_CLIP0_MAX, "PGRAPH Clip0 Max (Bits 30:16 = Y, Bits 10:0 = X)", NULL, NULL},
{ NV3_PGRAPH_CLIP1_MIN, "PGRAPH Clip1 Min (Bits 30:16 = Y, Bits 10:0 = X)", NULL, NULL},
{ NV3_PGRAPH_CLIP1_MAX, "PGRAPH Clip1 Max (Bits 30:16 = Y, Bits 10:0 = X)", NULL, NULL},
{ NV3_PGRAPH_FIFO_ACCESS, "PGRAPH - Can we access PFIFO?", NULL, NULL, },
{ NV3_PGRAPH_STATUS, "PGRAPH Status", NULL, NULL },
{ NV3_PGRAPH_TRAPPED_ADDRESS, "PGRAPH Trapped Address", NULL, NULL },
{ NV3_PGRAPH_TRAPPED_DATA, "PGRAPH Trapped Data", NULL, NULL },
{ NV3_PGRAPH_INSTANCE, "PGRAPH Object Instance", NULL, NULL},
{ NV3_PGRAPH_TRAPPED_INSTANCE, "PGRAPH Trapped Object Instance", NULL, NULL },
{ NV3_PGRAPH_DMA_INTR_0, "PGRAPH DMA Interrupt Status (unimplemented)", NULL, NULL },
{ NV3_PGRAPH_DMA_INTR_EN_0, "PGRAPH DMA Interrupt Enable (unimplemented)", NULL, NULL },
{ NV_REG_LIST_END, NULL, NULL, NULL}, // sentinel value
};
uint32_t nv3_pgraph_read(uint32_t address)
{
// before doing anything, check that this is even enabled..
if (!(nv3->pmc.enable >> NV3_PMC_ENABLE_PGRAPH)
& NV3_PMC_ENABLE_PGRAPH_ENABLED)
{
nv_log("Repressing PGRAPH read. The subsystem is disabled according to pmc_enable, returning 0\n");
return 0x00;
}
uint32_t ret = 0x00;
nv_register_t* reg = nv_get_register(address, pgraph_registers, sizeof(pgraph_registers)/sizeof(pgraph_registers[0]));
// todo: friendly logging
nv_log_verbose_only("PGRAPH Read from 0x%08x", address);
// if the register actually exists
if (reg)
{
// on-read function
if (reg->on_read)
ret = reg->on_read();
else
{
switch (reg->address)
{
case NV3_PGRAPH_DEBUG_0:
ret = nv3->pgraph.debug_0;
break;
case NV3_PGRAPH_DEBUG_1:
ret = nv3->pgraph.debug_1;
break;
case NV3_PGRAPH_DEBUG_2:
ret = nv3->pgraph.debug_2;
break;
case NV3_PGRAPH_DEBUG_3:
ret = nv3->pgraph.debug_3;
break;
//interrupt status and enable regs
case NV3_PGRAPH_INTR_0:
ret = nv3->pgraph.interrupt_status_0;
nv3_pmc_clear_interrupts();
break;
case NV3_PGRAPH_INTR_1:
ret = nv3->pgraph.interrupt_status_1;
nv3_pmc_clear_interrupts();
break;
case NV3_PGRAPH_INTR_EN_0:
ret = nv3->pgraph.interrupt_enable_0;
nv3_pmc_handle_interrupts(true);
break;
case NV3_PGRAPH_INTR_EN_1:
ret = nv3->pgraph.interrupt_enable_1;
nv3_pmc_handle_interrupts(true);
break;
// A lot of this is currently a temporary implementation so that we can just debug what the current state looks like
// during the driver initialisation process
// In the future, these will most likely have their own functions...
// Context Swithcing (THIS IS CONTROLLED BY PFIFO!)
case NV3_PGRAPH_CTX_SWITCH:
ret = nv3->pgraph.context_switch;
break;
case NV3_PGRAPH_CONTEXT_CONTROL:
ret = *(uint32_t*)&nv3->pgraph.context_control;
break;
case NV3_PGRAPH_CONTEXT_USER:
ret = *(uint32_t*)&nv3->pgraph.context_user;
break;
// Clip
case NV3_PGRAPH_ABS_UCLIP_XMIN:
ret = nv3->pgraph.abs_uclip_xmin;
break;
case NV3_PGRAPH_ABS_UCLIP_XMAX:
ret = nv3->pgraph.abs_uclip_xmax;
break;
case NV3_PGRAPH_ABS_UCLIP_YMIN:
ret = nv3->pgraph.abs_uclip_ymin;
break;
case NV3_PGRAPH_ABS_UCLIP_YMAX:
ret = nv3->pgraph.abs_uclip_ymax;
break;
// Canvas
case NV3_PGRAPH_SRC_CANVAS_MIN:
ret = *(uint32_t*)&nv3->pgraph.src_canvas_min;
break;
case NV3_PGRAPH_SRC_CANVAS_MAX:
ret = *(uint32_t*)&nv3->pgraph.src_canvas_max;
break;
// Pattern
case NV3_PGRAPH_PATTERN_COLOR_0_RGB:
ret = *(uint32_t*)&nv3->pgraph.pattern_color_0_rgb;
break;
case NV3_PGRAPH_PATTERN_COLOR_0_ALPHA:
ret = *(uint32_t*)&nv3->pgraph.pattern_color_0_alpha;
break;
case NV3_PGRAPH_PATTERN_COLOR_1_RGB:
ret = *(uint32_t*)&nv3->pgraph.pattern_color_1_rgb;
break;
case NV3_PGRAPH_PATTERN_COLOR_1_ALPHA:
ret = *(uint32_t*)&nv3->pgraph.pattern_color_1_alpha;
break;
case NV3_PGRAPH_PATTERN_BITMAP_HIGH:
ret = (nv3->pgraph.pattern_bitmap >> 32) & 0xFFFFFFFF;
break;
case NV3_PGRAPH_PATTERN_BITMAP_LOW:
ret = (nv3->pgraph.pattern_bitmap & 0xFFFFFFFF);
break;
// Beta factor
case NV3_PGRAPH_BETA:
ret = nv3->pgraph.beta_factor;
break;
// Todo: Massive table of ROP IDs or at least known ones?
case NV3_PGRAPH_ROP3:
ret = nv3->pgraph.rop;
break;
case NV3_PGRAPH_CHROMA_KEY:
ret = *(uint32_t*)&nv3->pgraph.chroma_key;
break;
case NV3_PGRAPH_PLANE_MASK:
ret = nv3->pgraph.plane_mask;
break;
// DMA
case NV3_PGRAPH_DMA:
ret = *(uint32_t*)&nv3->pgraph.dma_settings;
break;
case NV3_PGRAPH_NOTIFY:
ret = *(uint32_t*)&nv3->pgraph.notifier;
break;
// More clip
case NV3_PGRAPH_CLIP0_MIN:
ret = *(uint32_t*)&nv3->pgraph.clip0_min;
break;
case NV3_PGRAPH_CLIP0_MAX:
ret = *(uint32_t*)&nv3->pgraph.clip0_max;
break;
case NV3_PGRAPH_CLIP1_MIN:
ret = *(uint32_t*)&nv3->pgraph.clip1_min;
break;
case NV3_PGRAPH_CLIP1_MAX:
ret = *(uint32_t*)&nv3->pgraph.clip1_max;
break;
case NV3_PGRAPH_CLIP_MISC:
ret = *(uint32_t*)&nv3->pgraph.clip_misc_settings;
break;
// Overall Status
case NV3_PGRAPH_STATUS:
ret = *(uint32_t*)&nv3->pgraph.status;
break;
// Trapped Address
case NV3_PGRAPH_TRAPPED_ADDRESS:
ret = nv3->pgraph.trapped_address;
break;
case NV3_PGRAPH_TRAPPED_DATA:
ret = nv3->pgraph.trapped_data;
break;
case NV3_PGRAPH_INSTANCE:
ret = nv3->pgraph.instance;
break;
case NV3_PGRAPH_TRAPPED_INSTANCE:
ret = nv3->pgraph.trapped_instance;
break;
}
}
if (reg->friendly_name)
nv_log_verbose_only(": 0x%08x <- %s\n", ret, reg->friendly_name);
else
nv_log_verbose_only("\n");
}
else
{
/* Special exception for memory areas */
if (address >= NV3_PGRAPH_CONTEXT_CACHE(0)
&& address <= NV3_PGRAPH_CONTEXT_CACHE(NV3_PGRAPH_CONTEXT_CACHE_SIZE))
{
// Addresses should be aligned to 4 bytes.
uint32_t entry = (address - NV3_PGRAPH_CONTEXT_CACHE(0));
nv_log_verbose_only("PGRAPH Context Cache Read (Entry=%04x Value=%04x)\n", entry, nv3->pgraph.context_cache[entry]);
}
else /* Completely unknown */
{
nv_log(": Unknown register read (address=0x%08x), returning 0x00\n", address);
}
}
return ret;
}
void nv3_pgraph_write(uint32_t address, uint32_t value)
{
if (!(nv3->pmc.enable >> NV3_PMC_ENABLE_PGRAPH)
& NV3_PMC_ENABLE_PGRAPH_ENABLED)
{
nv_log("Repressing PGRAPH write. The subsystem is disabled according to pmc_enable\n");
return;
}
nv_register_t* reg = nv_get_register(address, pgraph_registers, sizeof(pgraph_registers)/sizeof(pgraph_registers[0]));
nv_log_verbose_only("PGRAPH Write 0x%08x -> 0x%08x\n", value, address);
// if the register actually exists
if (reg)
{
// on-read function
if (reg->on_write)
reg->on_write(value);
else
{
switch (reg->address)
{
case NV3_PGRAPH_DEBUG_0:
nv3->pgraph.debug_0 = value;
break;
case NV3_PGRAPH_DEBUG_1:
nv3->pgraph.debug_1 = value;
break;
case NV3_PGRAPH_DEBUG_2:
nv3->pgraph.debug_2 = value;
break;
case NV3_PGRAPH_DEBUG_3:
nv3->pgraph.debug_3 = value;
break;
//interrupt status and enable regs
case NV3_PGRAPH_INTR_0:
nv3->pgraph.interrupt_status_0 &= ~value;
//we changed interrupt state
nv3_pmc_clear_interrupts();
break;
case NV3_PGRAPH_INTR_1:
nv3->pgraph.interrupt_status_1 &= ~value;
//we changed interrupt state
nv3_pmc_clear_interrupts();
break;
// Only bits divisible by 4 matter
// and only bit0-16 is defined in intr_1
case NV3_PGRAPH_INTR_EN_0:
nv3->pgraph.interrupt_enable_0 = value & 0x11111111;
nv3_pmc_handle_interrupts(true);
break;
case NV3_PGRAPH_INTR_EN_1:
nv3->pgraph.interrupt_enable_1 = value & 0x00011111;
nv3_pmc_handle_interrupts(true);
break;
case NV3_PGRAPH_DMA_INTR_0:
nv3->pgraph.interrupt_status_dma &= ~value;
nv3_pmc_clear_interrupts();
break;
case NV3_PGRAPH_DMA_INTR_EN_0:
nv3->pgraph.interrupt_enable_dma = value & 0x000111111;
nv_log("Handling PGRAPH_DMA interrupts not implemented");
nv3_pmc_handle_interrupts(true);
break;
// A lot of this is currently a temporary implementation so that we can just debug what the current state looks like
// during the driver initialisation process
// In the future, these will most likely have their own functions...
// Context Swithcing (THIS IS CONTROLLED BY PFIFO!)
case NV3_PGRAPH_CTX_SWITCH:
nv3->pgraph.context_switch = value;
break;
case NV3_PGRAPH_CONTEXT_CONTROL:
*(uint32_t*)&nv3->pgraph.context_control = value;
break;
case NV3_PGRAPH_CONTEXT_USER:
*(uint32_t*)&nv3->pgraph.context_user = value;
break;
// Clip
case NV3_PGRAPH_ABS_UCLIP_XMIN:
nv3->pgraph.abs_uclip_xmin = value;
break;
case NV3_PGRAPH_ABS_UCLIP_XMAX:
nv3->pgraph.abs_uclip_xmax = value;
break;
case NV3_PGRAPH_ABS_UCLIP_YMIN:
nv3->pgraph.abs_uclip_ymin = value;
break;
case NV3_PGRAPH_ABS_UCLIP_YMAX:
nv3->pgraph.abs_uclip_ymax = value;
break;
// Canvas
case NV3_PGRAPH_SRC_CANVAS_MIN:
*(uint32_t*)&nv3->pgraph.src_canvas_min = value;
break;
case NV3_PGRAPH_SRC_CANVAS_MAX:
*(uint32_t*)&nv3->pgraph.src_canvas_max = value;
break;
// Pattern
case NV3_PGRAPH_PATTERN_COLOR_0_RGB:
*(uint32_t*)&nv3->pgraph.pattern_color_0_rgb = value;
break;
case NV3_PGRAPH_PATTERN_COLOR_0_ALPHA:
*(uint32_t*)&nv3->pgraph.pattern_color_0_alpha = value;
break;
case NV3_PGRAPH_PATTERN_COLOR_1_RGB:
*(uint32_t*)&nv3->pgraph.pattern_color_1_rgb = value;
break;
case NV3_PGRAPH_PATTERN_COLOR_1_ALPHA:
*(uint32_t*)&nv3->pgraph.pattern_color_1_alpha = value;
break;
case NV3_PGRAPH_PATTERN_BITMAP_HIGH:
nv3->pgraph.pattern_bitmap |= ((uint64_t)value << 32);
break;
case NV3_PGRAPH_PATTERN_BITMAP_LOW:
nv3->pgraph.pattern_bitmap |= value;
break;
// Beta factor
case NV3_PGRAPH_BETA:
nv3->pgraph.beta_factor = value;
break;
// Todo: Massive table of ROP IDs or at least known ones?
case NV3_PGRAPH_ROP3:
nv3->pgraph.rop = value & 0xFF;
break;
case NV3_PGRAPH_CHROMA_KEY:
nv3->pgraph.chroma_key = value;
break;
case NV3_PGRAPH_PLANE_MASK:
nv3->pgraph.plane_mask = value;
break;
// DMA
case NV3_PGRAPH_DMA:
*(uint32_t*)&nv3->pgraph.dma_settings = value;
break;
case NV3_PGRAPH_NOTIFY:
*(uint32_t*)&nv3->pgraph.notifier = value;
break;
// More clip
case NV3_PGRAPH_CLIP0_MIN:
*(uint32_t*)&nv3->pgraph.clip0_min = value;
break;
case NV3_PGRAPH_CLIP0_MAX:
*(uint32_t*)&nv3->pgraph.clip0_max = value;
break;
case NV3_PGRAPH_CLIP1_MIN:
*(uint32_t*)&nv3->pgraph.clip1_min = value;
break;
case NV3_PGRAPH_CLIP1_MAX:
*(uint32_t*)&nv3->pgraph.clip1_max = value;
break;
case NV3_PGRAPH_CLIP_MISC:
*(uint32_t*)&nv3->pgraph.clip_misc_settings = value;
break;
// Overall Status
case NV3_PGRAPH_STATUS:
*(uint32_t*)&nv3->pgraph.status = value;
break;
// Trapped Address
case NV3_PGRAPH_TRAPPED_ADDRESS:
nv3->pgraph.trapped_address = value;
break;
case NV3_PGRAPH_TRAPPED_DATA:
nv3->pgraph.trapped_data = value;
break;
case NV3_PGRAPH_INSTANCE:
nv3->pgraph.instance = value;
break;
case NV3_PGRAPH_TRAPPED_INSTANCE:
nv3->pgraph.trapped_instance = value;
break;
}
}
if (reg->friendly_name)
nv_log_verbose_only(": %s\n", reg->friendly_name);
else
nv_log_verbose_only("\n");
}
else
{
/* Special exception for memory areas */
if (address >= NV3_PGRAPH_CONTEXT_CACHE(0)
&& address <= NV3_PGRAPH_CONTEXT_CACHE(NV3_PGRAPH_CONTEXT_CACHE_SIZE))
{
// Addresses should be aligned to 4 bytes.
uint32_t entry = (address - NV3_PGRAPH_CONTEXT_CACHE(0)) >> 2;
nv_log_verbose_only("PGRAPH Context Cache Write (Entry=%04x Value=0x%08x)\n", entry, value);
nv3->pgraph.context_cache[entry] = value;
}
else /* Completely unknown */
{
nv_log(": Unknown register write (address=0x%08x)\n", address);
}
}
}
// Fire a VALID Pgraph interrupt: num is the bit# of the interrupt in the GPU subsystem INTR_EN register.
void nv3_pgraph_interrupt_valid(uint32_t num)
{
nv3->pgraph.interrupt_status_0 |= (1 << num);
nv3_pmc_handle_interrupts(true);
}
// Fire an INVALID pgraph interrupt
void nv3_pgraph_interrupt_invalid(uint32_t num)
{
nv3->pgraph.interrupt_status_1 |= (1 << num);
// Some code in pcbox hat enables the "reserved" bit HERE if it's set in intr 0. What???
nv3_pmc_handle_interrupts(true);
}
// VBlank. Fired every single frame.
void nv3_pgraph_vblank_start(svga_t* svga)
{
nv3_pgraph_interrupt_valid(NV3_PGRAPH_INTR_0_VBLANK);
}
/* Sends off method execution to the right class */
void nv3_pgraph_arbitrate_method(uint32_t param, uint16_t method, uint8_t channel, uint8_t subchannel, uint8_t class_id, nv3_ramin_context_t context)
{
/* Obtain the grobj information from the context in ramin */
nv3_grobj_t grobj = {0};
// we need to shift left by 4 to get the real address, something to do with the 16 byte unit of reversal
uint32_t real_ramin_base = context.ramin_offset << 4;
// readin our grobj
grobj.grobj_0 = nv3_ramin_read32(real_ramin_base, nv3);
grobj.grobj_1 = nv3_ramin_read32(real_ramin_base + 4, nv3);
grobj.grobj_2 = nv3_ramin_read32(real_ramin_base + 8, nv3);
grobj.grobj_3 = nv3_ramin_read32(real_ramin_base + 12, nv3);
nv_log_verbose_only("**** About to execute method **** method=0x%04x param=0x%08x, channel=%d.%d, class=%s, grobj=0x%08x 0x%08x 0x%08x 0x%08x\n",
method, param, channel, subchannel, nv3_class_names[class_id], grobj.grobj_0, grobj.grobj_1, grobj.grobj_2, grobj.grobj_3);
/* Methods below 0x104 are shared across all classids, so call generic_method for that*/
if (method <= NV3_SET_NOTIFY)
{
nv3_generic_method(param, method, context, grobj);
}
else
{
// By this point, we already ANDed the class ID to 0x1F.
// Send the grobj, the context, the method and the name off to actually be acted upon.
switch (class_id)
{
case nv3_pgraph_class01_beta_factor:
nv3_class_001_method(param, method, context, grobj);
break;
case nv3_pgraph_class02_rop:
nv3_class_002_method(param, method, context, grobj);
break;
case nv3_pgraph_class03_chroma_key:
nv3_class_003_method(param, method, context, grobj);
break;
case nv3_pgraph_class04_plane_mask:
nv3_class_004_method(param, method, context, grobj);
break;
case nv3_pgraph_class05_clipping_rectangle:
nv3_class_005_method(param, method, context, grobj);
break;
case nv3_pgraph_class06_pattern:
nv3_class_006_method(param, method, context, grobj);
break;
case nv3_pgraph_class07_rectangle:
nv3_class_007_method(param, method, context, grobj);
break;
case nv3_pgraph_class08_point:
nv3_class_008_method(param, method, context, grobj);
break;
case nv3_pgraph_class09_line:
nv3_class_009_method(param, method, context, grobj);
break;
case nv3_pgraph_class0a_lin:
nv3_class_00a_method(param, method, context, grobj);
break;
case nv3_pgraph_class0b_triangle:
nv3_class_00b_method(param, method, context, grobj);
break;
case nv3_pgraph_class0c_w95txt:
nv3_class_00c_method(param, method, context, grobj);
break;
case nv3_pgraph_class0d_m2mf:
nv3_class_00d_method(param, method, context, grobj);
break;
case nv3_pgraph_class0e_scaled_image_from_memory:
nv3_class_00e_method(param, method, context, grobj);
break;
case nv3_pgraph_class10_blit:
nv3_class_010_method(param, method, context, grobj);
break;
case nv3_pgraph_class11_image:
nv3_class_011_method(param, method, context, grobj);
break;
case nv3_pgraph_class12_bitmap:
nv3_class_012_method(param, method, context, grobj);
break;
case nv3_pgraph_class14_transfer2memory:
nv3_class_014_method(param, method, context, grobj);
break;
case nv3_pgraph_class15_stretched_image_from_cpu:
nv3_class_015_method(param, method, context, grobj);
break;
case nv3_pgraph_class17_d3d5tri_zeta_buffer:
nv3_class_017_method(param, method, context, grobj);
break;
case nv3_pgraph_class18_point_zeta_buffer:
nv3_class_018_method(param, method, context, grobj);
break;
case nv3_pgraph_class1c_image_in_memory:
nv3_class_01c_method(param, method, context, grobj);
break;
default:
fatal("NV3 (nv3_pgraph_arbitrate_method): Attempted to execute method on invalid, or unimplemented, class ID %s", nv3_class_names[class_id]);
return;
}
}
nv3_notify_if_needed(param, method, context, grobj);
}
/* Arbitrates graphics object submission to the right object types */
void nv3_pgraph_submit(uint32_t param, uint16_t method, uint8_t channel, uint8_t subchannel, uint8_t class_id, nv3_ramin_context_t context)
{
// class id can be derived from the context but we debug log it before we get here
// Do we need to read grobj here?
switch (method)
{
default:
// Object Method arbitration
nv3_pgraph_arbitrate_method(param, method, channel, subchannel, class_id, context);
break;
}
}

View File

@@ -0,0 +1,274 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* NV3 PMC - Master control for the chip
*
*
*
* Authors: Connor Hyde, <mario64crashed@gmail.com> I need a better email address ;^)
*
* Copyright 2024-2025 starfrost
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/pci.h>
#include <86box/rom.h> // DEPENDENT!!!
#include <86box/video.h>
#include <86box/nv/vid_nv.h>
#include <86box/nv/vid_nv3.h>
void nv3_pmc_init(void)
{
nv_log("Initialising PMC....\n");
if (nv3->nvbase.gpu_revision == NV3_PCI_CFG_REVISION_A00)
nv3->pmc.boot = NV3_BOOT_REG_REV_A00;
else if (nv3->nvbase.gpu_revision == NV3_PCI_CFG_REVISION_B00)
nv3->pmc.boot = NV3_BOOT_REG_REV_B00;
else
nv3->pmc.boot = NV3_BOOT_REG_REV_C00;
nv3->pmc.interrupt_enable = NV3_PMC_INTERRUPT_ENABLE_HARDWARE | NV3_PMC_INTERRUPT_ENABLE_SOFTWARE;
nv_log("Initialising PMC: Done\n");
}
//
// ****** PMC register list START ******
//
nv_register_t pmc_registers[] = {
{ NV3_PMC_BOOT, "PMC: Boot Manufacturing Information", NULL, NULL },
{ NV3_PMC_INTERRUPT_STATUS, "PMC: Current Pending Subsystem Interrupts", NULL, NULL},
{ NV3_PMC_INTERRUPT_ENABLE, "PMC: Global Interrupt Enable", NULL, NULL,},
{ NV3_PMC_ENABLE, "PMC: Global Subsystem Enable", NULL, NULL },
{ NV_REG_LIST_END, NULL, NULL, NULL}, // sentinel value
};
void nv3_pmc_clear_interrupts(void)
{
nv_log_verbose_only("Clearing IRQs\n");
pci_clear_irq(nv3->nvbase.pci_slot, PCI_INTA, &nv3->nvbase.pci_irq_state);
}
// Handle hardware interrupts
// We only clear when we need to, in other functions...
uint32_t nv3_pmc_handle_interrupts(bool send_now)
{
// TODO:
// PGRAPH DMA INTR_EN (there is no DMA engine yet)
// PRM Real-Mode Compatibility Interrupts
uint32_t new_intr_value = 0x00;
// set the new interrupt value
// PAUDIO not used
// IF NV3 REV A EMULATION IS ADDED, ADD THIS COMPONENT!
// the registers are designed to line up so you can enable specific interrupts
// Check Mediaport interrupts
if (nv3->pme.interrupt_status & nv3->pme.interrupt_enable)
new_intr_value |= (NV3_PMC_INTERRUPT_PMEDIA_PENDING << NV3_PMC_INTERRUPT_PMEDIA);
// Check FIFO interrupts
if (nv3->pfifo.interrupt_status & nv3->pfifo.interrupt_enable)
new_intr_value |= (NV3_PMC_INTERRUPT_PFIFO_PENDING << NV3_PMC_INTERRUPT_PFIFO);
// PFB interrupt is VBLANK PGRAPH interrupt...what nvidia...
if (nv3->pgraph.interrupt_status_0 & (1 << 8)
&& nv3->pgraph.interrupt_enable_0 & (1 << 8))
new_intr_value |= (NV3_PMC_INTERRUPT_PFB_PENDING << NV3_PMC_INTERRUPT_PFB);
if (nv3->pgraph.interrupt_status_0 & ~(1 << 8)
&& nv3->pgraph.interrupt_enable_0 & ~(1 << 8)) // otherwise PGRAPH-0 interurpt
new_intr_value |= (NV3_PMC_INTERRUPT_PGRAPH0_PENDING << NV3_PMC_INTERRUPT_PGRAPH0);
// Check second pgraph interrupt register
if (nv3->pgraph.interrupt_status_1 & nv3->pgraph.interrupt_enable_1)
new_intr_value |= (NV3_PMC_INTERRUPT_PGRAPH1_PENDING << NV3_PMC_INTERRUPT_PGRAPH1);
// check video overlay interrupts
if (nv3->pvideo.interrupt_status & nv3->pvideo.interrupt_enable)
new_intr_value |= (NV3_PMC_INTERRUPT_PVIDEO_PENDING << NV3_PMC_INTERRUPT_PVIDEO);
// check PIT interrupts
if (nv3->ptimer.interrupt_status & nv3->ptimer.interrupt_enable)
new_intr_value |= (NV3_PMC_INTERRUPT_PTIMER_PENDING << NV3_PMC_INTERRUPT_PTIMER);
// check bus interrupts
if (nv3->pbus.interrupt_status & nv3->pbus.interrupt_enable)
new_intr_value |= (NV3_PMC_INTERRUPT_PBUS_PENDING << NV3_PMC_INTERRUPT_PBUS);
// check SW interrupts
if (nv3->pmc.interrupt_status & (1 << NV3_PMC_INTERRUPT_SOFTWARE))
new_intr_value |= (NV3_PMC_INTERRUPT_SOFTWARE_PENDING << NV3_PMC_INTERRUPT_SOFTWARE);
nv3->pmc.interrupt_status = new_intr_value;
// ***TODO: DOes INTR still change if INTR_EN=0???***
// If interrupts are disabled don't bother
if (!nv3->pmc.interrupt_enable)
{
nv3_pmc_clear_interrupts();
return nv3->pmc.interrupt_status;
}
// if we actually need to send the interrupt (i.e. this is a write) send it now
if (send_now)
{
// no interrupts to send
if (!(nv3->pmc.interrupt_status)
|| !(nv3->pmc.interrupt_status - 0x80000000))
{
nv3_pmc_clear_interrupts();
return nv3->pmc.interrupt_status;
}
if ((nv3->pmc.interrupt_status & 0x7FFFFFFF))
{
if (nv3->pmc.interrupt_enable & NV3_PMC_INTERRUPT_ENABLE_HARDWARE)
{
nv_log_verbose_only("Firing hardware-originated interrupt NV3_PMC_INTR_0=0x%08x\n", nv3->pmc.interrupt_status);
pci_set_irq(nv3->nvbase.pci_slot, PCI_INTA, &nv3->nvbase.pci_irq_state);
}
else
nv_log_verbose_only("NOT firing hardware-originated interrupt NV3_PMC_INTR_0=0x%08x, BECAUSE HARDWARE INTERRUPTS ARE DISABLED\n", nv3->pmc.interrupt_status);
}
else
{
if (nv3->pmc.interrupt_enable & NV3_PMC_INTERRUPT_ENABLE_SOFTWARE)
{
nv_log_verbose_only("Firing software-originated interrupt NV3_PMC_INTR_0=0x%08x\n", nv3->pmc.interrupt_status);
pci_set_irq(nv3->nvbase.pci_slot, PCI_INTA, &nv3->nvbase.pci_irq_state);
}
else
nv_log_verbose_only("NOT firing software-originated interrupt NV3_PMC_INTR_0=0x%08x, BECAUSE SOFTWARE INTERRUPTS ARE DISABLED\n", nv3->pmc.interrupt_status);
}
}
return nv3->pmc.interrupt_status;
}
//
// ****** Read/Write functions start ******
//
uint32_t nv3_pmc_read(uint32_t address)
{
nv_register_t* reg = nv_get_register(address, pmc_registers, sizeof(pmc_registers)/sizeof(pmc_registers[0]));
uint32_t ret = 0x00;
// todo: friendly logging
nv_log_verbose_only("PMC Read from 0x%08x", address);
// if the register actually exists
if (reg)
{
// on-read function
if (reg->on_read)
ret = reg->on_read();
else
{
switch (reg->address)
{
case NV3_PMC_BOOT:
ret = nv3->pmc.boot;
break;
case NV3_PMC_INTERRUPT_STATUS:
nv_log_verbose_only("\n"); // clear_interrupts logs
nv3_pmc_clear_interrupts();
ret = nv3_pmc_handle_interrupts(false);
break;
case NV3_PMC_INTERRUPT_ENABLE:
//TODO: ACTUALLY CHANGE THE INTERRUPT STATE
ret = nv3->pmc.interrupt_enable;
break;
case NV3_PMC_ENABLE:
ret = nv3->pmc.enable;
break;
}
}
if (reg->friendly_name)
nv_log_verbose_only(": 0x%08x <- %s\n", ret, reg->friendly_name);
else
nv_log_verbose_only("\n");
}
else
{
nv_log(": Unknown register read (address=0x%08x), returning 0x00\n", address);
}
return ret;
}
void nv3_pmc_write(uint32_t address, uint32_t value)
{
nv_register_t* reg = nv_get_register(address, pmc_registers, sizeof(pmc_registers)/sizeof(pmc_registers[0]));
nv_log_verbose_only("PMC Write 0x%08x -> 0x%08x", value, address);
// if the register actually exists...
if (reg)
{
// ... call its on-write function
if (reg->on_write)
reg->on_write(value);
else
{
// if it doesn't have one fallback to a switch statement
switch (reg->address)
{
case NV3_PMC_INTERRUPT_STATUS:
// This can only be done by software interrupts...
if (!(nv3->pmc.interrupt_status & 0x7FFFFFFF))
{
warning("Huh? This is a hardware interrupt...Please use the INTR_EN registers of the GPU subsystem you want to trigger "
" an interrupt on, rather than writing to NV3_PMC_INTERRUPT_STATUS (Or this is a bug)...NV3_PMC_INTERRUPT_STATUS=0x%08x)\n", nv3->pmc.interrupt_enable);
return;
}
nv3_pmc_handle_interrupts(true);
nv3->pmc.interrupt_status = value;
break;
case NV3_PMC_INTERRUPT_ENABLE:
nv3->pmc.interrupt_enable = value & 0x03;
nv3_pmc_handle_interrupts(value != 0);
break;
case NV3_PMC_ENABLE:
nv3->pmc.enable = value;
break;
}
}
if (reg->friendly_name)
nv_log_verbose_only(": %s\n", reg->friendly_name);
else
nv_log_verbose_only("\n");
}
else /* Completely unknown */
{
nv_log(": Unknown register write (address=0x%08x)\n", address);
}
}

View File

@@ -0,0 +1,136 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* NV3 pme: Nvidia Mediaport - External MPEG Decode Interface
*
*
*
* Authors: Connor Hyde, <mario64crashed@gmail.com> I need a better email address ;^)
*
* Copyright 2024-2025 starfrost
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/pci.h>
#include <86box/rom.h> // DEPENDENT!!!
#include <86box/video.h>
#include <86box/nv/vid_nv.h>
#include <86box/nv/vid_nv3.h>
nv_register_t pme_registers[] = {
{ NV3_PME_INTR, "PME - Interrupt Status", NULL, NULL},
{ NV3_PME_INTR_EN, "PME - Interrupt Enable", NULL, NULL,},
{ NV_REG_LIST_END, NULL, NULL, NULL}, // sentinel value
};
void nv3_pme_init(void)
{
nv_log("Initialising PME...");
nv_log("Done\n");
}
uint32_t nv3_pme_read(uint32_t address)
{
nv_register_t* reg = nv_get_register(address, pme_registers, sizeof(pme_registers)/sizeof(pme_registers[0]));
uint32_t ret = 0x00;
// todo: friendly logging
nv_log_verbose_only("PME Read from 0x%08x", address);
// if the register actually exists
if (reg)
{
// on-read function
if (reg->on_read)
ret = reg->on_read();
else
{
// Interrupt state:
// Bit 0 - Image Notifier
// Bit 4 - Vertical Blank Interval Notifier
// Bit 8 - Video Notifier
// Bit 12 - Audio Notifier
// Bit 16 - VMI Notifer
switch (reg->address)
{
case NV3_PME_INTR:
ret = nv3->pme.interrupt_status;
break;
case NV3_PME_INTR_EN:
ret = nv3->pme.interrupt_enable;
break;
}
}
if (reg->friendly_name)
nv_log_verbose_only(": 0x%08x <- %s\n", ret, reg->friendly_name);
else
nv_log_verbose_only("\n");
}
else
{
nv_log(": Unknown register read (address=0x%08x), returning 0x00\n", address);
}
return ret;
}
void nv3_pme_write(uint32_t address, uint32_t value)
{
nv_register_t* reg = nv_get_register(address, pme_registers, sizeof(pme_registers)/sizeof(pme_registers[0]));
nv_log_verbose_only("PME Write 0x%08x -> 0x%08x\n", value, address);
// if the register actually exists
if (reg)
{
if (reg->friendly_name)
nv_log_verbose_only(": %s\n", reg->friendly_name);
else
nv_log_verbose_only("\n");
// on-read function
if (reg->on_write)
reg->on_write(value);
else
{
switch (reg->address)
{
// Interrupt state:
// Bit 0 - Image Notifier
// Bit 4 - Vertical Blank Interfal Notifier
// Bit 8 - Video Notifier
// Bit 12 - Audio Notifier
// Bit 16 - VMI Notifer
case NV3_PME_INTR:
nv3->pme.interrupt_status &= ~value;
nv3_pmc_clear_interrupts();
break;
case NV3_PME_INTR_EN:
nv3->pme.interrupt_enable = value & 0x00001111;
break;
}
}
}
else /* Completely unknown */
{
nv_log(": Unknown register write (address=0x%08x)\n", address);
}
}

View File

@@ -0,0 +1,458 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* NV3 bringup and device emulation.
*
*
*
* Authors: Connor Hyde, <mario64crashed@gmail.com> I need a better email address ;^)
*
* Copyright 2024-2025 starfrost
*/
// nv3_pramdac.c: NV3 RAMDAC
// Todo: Allow overridability using 68050C register...
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/pci.h>
#include <86box/rom.h> // DEPENDENT!!!
#include <86box/video.h>
#include <86box/nv/vid_nv.h>
#include <86box/nv/vid_nv3.h>
void nv3_pramdac_init(void)
{
nv_log("Initialising PRAMDAC\n");
// defaults, these come from vbios in reality
// driver defaults are nonsensical(?), or the algorithm is wrong
// munged this to 100mhz for now
nv3->pramdac.memory_clock_m = nv3->pramdac.pixel_clock_m = 0x07;
nv3->pramdac.memory_clock_n = nv3->pramdac.pixel_clock_n = 0xc8;
nv3->pramdac.memory_clock_p = nv3->pramdac.pixel_clock_p = 0x0c;
nv3_pramdac_set_pixel_clock();
nv3_pramdac_set_vram_clock();
nv_log("Initialising PRAMDAC: Done\n");
}
// Polls the pixel clock.
void nv3_pramdac_pixel_clock_poll(double real_time)
{
/* Ignore in VGA mode */
if (!nv3->nvbase.svga.override)
return;
/* Figure out our refresh time. */
if (!nv3->nvbase.refresh_time)
nv3->nvbase.refresh_time = (1/60.0); // rivatimers count in microseconds but present the info as seconds
nv3->nvbase.refresh_clock += real_time;
if (nv3->nvbase.refresh_clock > nv3->nvbase.refresh_time)
{
/* Update the screen because something changed */
nv3_render_current_bpp();
video_blit_memtoscreen(0, 0, xsize, ysize);
nv3->nvbase.refresh_clock = 0;
}
// TODO: ????
}
// Polls the memory clock.
// This updates the 2D/3D engine PGRAPH, PTIMER and more
void nv3_pramdac_memory_clock_poll(double real_time)
{
nv3_ptimer_tick(real_time);
nv3_pfifo_cache0_pull();
nv3_pfifo_cache1_pull();
// TODO: UPDATE PGRAPH!
}
// Gets the vram clock register.
uint32_t nv3_pramdac_get_vram_clock_register(void)
{
// the clock format is packed into 19 bits
// M divisor [7-0]
// N divisor [16-8]
// P divisor [18-16]
return (nv3->pramdac.memory_clock_m)
+ (nv3->pramdac.memory_clock_n << 8)
+ (nv3->pramdac.memory_clock_p << 16); // 0-3
}
uint32_t nv3_pramdac_get_pixel_clock_register(void)
{
return (nv3->pramdac.pixel_clock_m)
+ (nv3->pramdac.pixel_clock_n << 8)
+ (nv3->pramdac.pixel_clock_p << 16); // 0-3
}
void nv3_pramdac_set_vram_clock_register(uint32_t value)
{
nv3->pramdac.memory_clock_m = value & 0xFF;
nv3->pramdac.memory_clock_n = (value >> 8) & 0xFF;
nv3->pramdac.memory_clock_p = (value >> 16) & 0x07;
nv3_pramdac_set_vram_clock();
}
void nv3_pramdac_set_pixel_clock_register(uint32_t value)
{
nv3->pramdac.pixel_clock_m = value & 0xFF;
nv3->pramdac.pixel_clock_n = (value >> 8) & 0xFF;
nv3->pramdac.pixel_clock_p = (value >> 16) & 0x07;
nv3_pramdac_set_pixel_clock();
}
void nv3_pramdac_set_vram_clock(void)
{
// from driver and vbios source
float frequency = 13500000.0f;
// prevent division by 0
if (nv3->pramdac.memory_clock_m == 0)
nv3->pramdac.memory_clock_m = 1;
if (nv3->pramdac.memory_clock_n == 0)
nv3->pramdac.memory_clock_n = 1;
// Convert to microseconds
frequency = (frequency * nv3->pramdac.memory_clock_n) / (nv3->pramdac.memory_clock_m << nv3->pramdac.memory_clock_p);
double time = 1000000.0 / (double)frequency; // needs to be a double for 86box
nv_log("Memory clock = %.2f MHz\n", frequency / 1000000.0f);
nv3->nvbase.memory_clock_frequency = frequency;
// Create and start if it it's not running.
if (!nv3->nvbase.memory_clock_timer)
{
nv3->nvbase.memory_clock_timer = rivatimer_create(time, nv3_pramdac_memory_clock_poll);
rivatimer_start(nv3->nvbase.memory_clock_timer);
}
rivatimer_set_period(nv3->nvbase.memory_clock_timer, time);
}
void nv3_pramdac_set_pixel_clock(void)
{
// frequency divider algorithm from old varcem/86box/pcbox riva driver,
// verified by reversing NT drivers v1.50e CalcMNP [symbols] function
// missing section
// not really needed.
// if (nv3->pfb.boot.clock_crystal == CLOCK_CRYSTAL_13500)
// {
// freq = 13500000.0f;
// }
// else
//
// {
// freq = 14318000.0f;
// }
float frequency = 13500000.0f;
// prevent division by 0
if (nv3->pramdac.pixel_clock_m == 0)
nv3->pramdac.pixel_clock_m = 1;
if (nv3->pramdac.memory_clock_n == 0)
nv3->pramdac.memory_clock_n = 1;
frequency = (frequency * nv3->pramdac.pixel_clock_n) / (nv3->pramdac.pixel_clock_m << nv3->pramdac.pixel_clock_p);
nv3->nvbase.svga.clock = cpuclock / frequency;
double time = 1000000.0 / (double)frequency; // needs to be a double for 86box
nv_log("Pixel clock = %.2f MHz\n", frequency / 1000000.0f);
nv3->nvbase.pixel_clock_frequency = frequency;
// Create and start if it it's not running.
if (!nv3->nvbase.pixel_clock_timer)
{
nv3->nvbase.pixel_clock_timer = rivatimer_create(time, nv3_pramdac_pixel_clock_poll);
rivatimer_start(nv3->nvbase.pixel_clock_timer);
}
rivatimer_set_period(nv3->nvbase.pixel_clock_timer, time);
}
//
// ****** PRAMDAC register list START ******
//
// NULL means handle in read functions
nv_register_t pramdac_registers[] =
{
{ NV3_PRAMDAC_CURSOR_START, "PRAMDAC - Cursor Start Position"},
{ NV3_PRAMDAC_CLOCK_PIXEL, "PRAMDAC - NV3 GPU Core - Pixel clock", nv3_pramdac_get_pixel_clock_register, nv3_pramdac_set_pixel_clock_register },
{ NV3_PRAMDAC_CLOCK_MEMORY, "PRAMDAC - NV3 GPU Core - Memory clock", nv3_pramdac_get_vram_clock_register, nv3_pramdac_set_vram_clock_register },
{ NV3_PRAMDAC_COEFF_SELECT, "PRAMDAC - PLL Clock Coefficient Select", NULL, NULL},
{ NV3_PRAMDAC_GENERAL_CONTROL, "PRAMDAC - General Control", NULL, NULL },
{ NV3_PRAMDAC_VSERR_WIDTH, "PRAMDAC - Vertical Sync Error Width", NULL, NULL},
{ NV3_PRAMDAC_VEQU_END, "PRAMDAC - VEqu End", NULL, NULL},
{ NV3_PRAMDAC_VBBLANK_START, "PRAMDAC - VBBlank Start", NULL, NULL},
{ NV3_PRAMDAC_VBBLANK_END, "PRAMDAC - VBBlank End", NULL, NULL},
{ NV3_PRAMDAC_HBLANK_END, "PRAMDAC - Horizontal Blanking Interval End", NULL, NULL},
{ NV3_PRAMDAC_HBLANK_START, "PRAMDAC - Horizontal Blanking Interval Start", NULL, NULL},
{ NV3_PRAMDAC_VBLANK_END, "PRAMDAC - Vertical Blanking Interval End", NULL, NULL},
{ NV3_PRAMDAC_VBLANK_START, "PRAMDAC - Vertical Blanking Interval Start", NULL, NULL},
{ NV3_PRAMDAC_VEQU_START, "PRAMDAC - VEqu Start", NULL, NULL},
{ NV3_PRAMDAC_VTOTAL, "PRAMDAC - Total Vertical Lines", NULL, NULL},
{ NV3_PRAMDAC_HSYNC_WIDTH, "PRAMDAC - Horizontal Sync Pulse Width", NULL, NULL},
{ NV3_PRAMDAC_HBURST_START, "PRAMDAC - Horizontal Burst Signal Start", NULL, NULL},
{ NV3_PRAMDAC_HBURST_END, "PRAMDAC - Horizontal Burst Signal Start", NULL, NULL},
{ NV3_PRAMDAC_HTOTAL, "PRAMDAC - Total Horizontal Lines", NULL, NULL},
{ NV3_PRAMDAC_HEQU_WIDTH, "PRAMDAC - HEqu End", NULL, NULL},
{ NV3_PRAMDAC_HSERR_WIDTH, "PRAMDAC - Horizontal Sync Error", NULL, NULL},
{ NV3_USER_DAC_PIXEL_MASK, "PRAMDAC - User DAC Pixel Mask", NULL, NULL},
{ NV3_USER_DAC_READ_MODE_ADDRESS, "PRAMDAC - User DAC Read Mode Address", NULL, NULL},
{ NV3_USER_DAC_WRITE_MODE_ADDRESS, "PRAMDAC - User DAC Write Mode Address", NULL, NULL},
{ NV3_USER_DAC_PALETTE_DATA, "PRAMDAC - User DAC Palette Data", NULL, NULL},
{ NV_REG_LIST_END, NULL, NULL, NULL}, // sentinel value
};
//
// ****** Read/Write functions start ******
//
uint32_t nv3_pramdac_read(uint32_t address)
{
nv_register_t* reg = nv_get_register(address, pramdac_registers, sizeof(pramdac_registers)/sizeof(pramdac_registers[0]));
uint32_t ret = 0x00;
// todo: friendly logging
nv_log_verbose_only("PRAMDAC Read from 0x%08x\n", address);
// if the register actually exists
if (reg)
{
// on-read function
if (reg->on_read)
ret = reg->on_read();
else
{
//s hould be pretty easy to understand
switch (reg->address)
{
case NV3_PRAMDAC_COEFF_SELECT:
ret = nv3->pramdac.coeff_select;
break;
case NV3_PRAMDAC_GENERAL_CONTROL:
ret = nv3->pramdac.general_control;
break;
case NV3_PRAMDAC_VSERR_WIDTH:
ret = nv3->pramdac.vserr_width;
break;
case NV3_PRAMDAC_VBBLANK_END:
ret = nv3->pramdac.vbblank_end;
break;
case NV3_PRAMDAC_VBLANK_END:
ret = nv3->pramdac.vblank_end;
break;
case NV3_PRAMDAC_VBLANK_START:
ret = nv3->pramdac.vblank_start;
break;
case NV3_PRAMDAC_VEQU_START:
ret = nv3->pramdac.vequ_start;
break;
case NV3_PRAMDAC_VTOTAL:
ret = nv3->pramdac.vtotal;
break;
case NV3_PRAMDAC_HSYNC_WIDTH:
ret = nv3->pramdac.hsync_width;
break;
case NV3_PRAMDAC_HBURST_START:
ret = nv3->pramdac.hburst_start;
break;
case NV3_PRAMDAC_HBURST_END:
ret = nv3->pramdac.hburst_end;
break;
case NV3_PRAMDAC_HBLANK_START:
ret = nv3->pramdac.hblank_start;
break;
case NV3_PRAMDAC_HBLANK_END:
ret = nv3->pramdac.hblank_end;
break;
case NV3_PRAMDAC_HTOTAL:
ret = nv3->pramdac.htotal;
break;
case NV3_PRAMDAC_HEQU_WIDTH:
ret = nv3->pramdac.hequ_width;
break;
case NV3_PRAMDAC_HSERR_WIDTH:
ret = nv3->pramdac.hserr_width;
break;
case NV3_USER_DAC_PIXEL_MASK:
ret = nv3->pramdac.user_pixel_mask;
break;
case NV3_USER_DAC_READ_MODE_ADDRESS:
ret = nv3->pramdac.user_read_mode_address;
break;
case NV3_USER_DAC_WRITE_MODE_ADDRESS:
ret = nv3->pramdac.user_write_mode_address;
break;
case NV3_USER_DAC_PALETTE_DATA:
/* I doubt NV actually read this in their drivers, but it's worth doing anyway */
/* Bit 1 is listed as "read or write mode" and 7:0 as "Write-only address", but NV only ever set this to 0 too, so i think this should be fine for now */
ret = nv3->pramdac.palette[nv3->pramdac.user_read_mode_address];
nv3->pramdac.user_read_mode_address++;
break;
case NV3_PRAMDAC_CURSOR_START:
ret = (nv3->pramdac.cursor_start.y << 16) | nv3->pramdac.cursor_start.x;
break;
}
}
if (reg->friendly_name)
nv_log_verbose_only(": 0x%08x <- %s\n", ret, reg->friendly_name);
else
nv_log_verbose_only("\n");
}
else
{
nv_log(": Unknown register read (address=0x%08x), returning 0x00\n", address);
}
return ret;
}
void nv3_pramdac_write(uint32_t address, uint32_t value)
{
nv_register_t* reg = nv_get_register(address, pramdac_registers, sizeof(pramdac_registers)/sizeof(pramdac_registers[0]));
nv_log_verbose_only("PRAMDAC Write 0x%08x -> 0x%08x\n", value, address);
// if the register actually exists
if (reg)
{
// on-read function
if (reg->on_write)
reg->on_write(value);
else
{
//s hould be pretty easy to understand
// we also update the SVGA state here
switch (reg->address)
{
case NV3_PRAMDAC_COEFF_SELECT:
nv3->pramdac.coeff_select = value;
break;
case NV3_PRAMDAC_GENERAL_CONTROL:
nv3->pramdac.general_control = value;
nv3_recalc_timings(&nv3->nvbase.svga);
break;
case NV3_PRAMDAC_VSERR_WIDTH:
//vslines?
nv3->pramdac.vserr_width = value;
break;
case NV3_PRAMDAC_VBBLANK_END:
nv3->pramdac.vbblank_end = value;
break;
case NV3_PRAMDAC_VBLANK_END:
nv3->pramdac.vblank_end = value;
break;
case NV3_PRAMDAC_VBLANK_START:
//nv3->nvbase.svga.vblankstart = value;
nv3->pramdac.vblank_start = value;
break;
case NV3_PRAMDAC_VEQU_START:
nv3->pramdac.vequ_start = value;
break;
case NV3_PRAMDAC_VTOTAL:
//nv3->pramdac.vtotal = value;
nv3->nvbase.svga.vtotal = value;
break;
case NV3_PRAMDAC_HSYNC_WIDTH:
nv3->pramdac.hsync_width = value;
break;
case NV3_PRAMDAC_HBURST_START:
nv3->pramdac.hburst_start = value;
break;
case NV3_PRAMDAC_HBURST_END:
nv3->pramdac.hburst_end = value;
break;
case NV3_PRAMDAC_HBLANK_START:
//nv3->nvbase.svga.hblankstart = value;
nv3->pramdac.hblank_start = value;
break;
case NV3_PRAMDAC_HBLANK_END:
//nv3->nvbase.svga.hblank_end_val = value;
nv3->pramdac.hblank_end = value;
break;
case NV3_PRAMDAC_HTOTAL:
nv3->pramdac.htotal = value;
//nv3->nvbase.svga.htotal = value;
break;
case NV3_PRAMDAC_HEQU_WIDTH:
nv3->pramdac.hequ_width = value;
break;
case NV3_PRAMDAC_HSERR_WIDTH:
nv3->pramdac.hserr_width = value;
break;
case NV3_USER_DAC_PIXEL_MASK:
nv3->pramdac.user_pixel_mask = value;
break;
case NV3_USER_DAC_READ_MODE_ADDRESS:
nv3->pramdac.user_read_mode_address = value;
break;
case NV3_USER_DAC_WRITE_MODE_ADDRESS:
/*
This seems to get reset to 0 after 256 writes, but, the palette is 768 bytes in size.
Clearly there's some mechanism here, but I'm not sure what it is. So let's just reset if we reach 768.
*/
if (nv3->pramdac.user_write_mode_address >= NV3_USER_DAC_PALETTE_SIZE)
nv3->pramdac.user_write_mode_address = value;
break;
case NV3_USER_DAC_PALETTE_DATA:
/* I doubt NV actually read this in their drivers, but it's worth doing anyway */
/* Bit 1 is listed as "read or write mode" and 7:0 as "Write-only address", but NV only ever set this to 0 too, so i think this should be fine for now */
nv3->pramdac.palette[nv3->pramdac.user_write_mode_address] = value;
nv3->pramdac.user_write_mode_address++;
break;
/* cursor start location */
case NV3_PRAMDAC_CURSOR_START:
// only 12 bits are used here instead of 16 for some stupid reason
nv3->pramdac.cursor_start.y = (value >> 16) & 0xFFF;
nv3->pramdac.cursor_start.x = (value) & 0xFFF;
nv3_draw_cursor(&nv3->nvbase.svga, 0);//drawline doesn't matter here
break;
}
}
if (reg->friendly_name)
nv_log_verbose_only(": %s\n", reg->friendly_name);
else
nv_log_verbose_only("\n");
}
else /* Completely unknown */
{
nv_log(": Unknown register write (address=0x%08x)\n", address);
}
}

View File

@@ -0,0 +1,515 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* NV3 PRAMIN - Basically, this is how we know what to render.
* Has a giant hashtable of all the submitted DMA objects using a pseudo-C++ class system
*
*
*
* Authors: Connor Hyde, <mario64crashed@gmail.com> I need a better email address ;^)
*
* Copyright 2024-2025 starfrost
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/pci.h>
#include <86box/rom.h> // DEPENDENT!!!
#include <86box/video.h>
#include <86box/nv/vid_nv.h>
#include <86box/nv/vid_nv3.h>
#include <86box/nv/classes/vid_nv3_classes.h>
// Functions only used in this translation unit
#ifndef RELEASE_BUILD
void nv3_debug_ramin_print_context_info(uint32_t name, nv3_ramin_context_t context);
#endif
// i believe the main loop is to walk the hashtable in RAMIN (last 0.5 MB of VRAM),
// find the objects that were submitted from DMA
// (going from software -> nvidia d3d / ogl implementation -> resource manager client -> nvapi -> nvrm -> GPU PFIFO -> GPU PBUS -> GPU PFB RAMIN -> PGRAPH)
// and then rendering each of those using PGRAPH
// Notes for all of these functions:
// Structures in RAMIN are stored from the bottom of vram up in reverse order
// this can be explained without bitwise math like so:
// real VRAM address = VRAM_size - (ramin_address - (ramin_address % reversal_unit_size)) - reversal_unit_size + (ramin_address % reversal_unit_size)
// reversal unit size in this case is 16 bytes, vram size is 2-8mb (but 8mb is zx/nv3t only and 2mb...i haven't found a 22mb card)
// Read 8-bit ramin
uint8_t nv3_ramin_read8(uint32_t addr, void* priv)
{
if (!nv3) return 0x00;
addr &= (nv3->nvbase.svga.vram_max - 1);
uint32_t raw_addr = addr; // saved after and
addr ^= (nv3->nvbase.svga.vram_max - 0x10);
uint32_t val = 0x00;
if (!nv3_ramin_arbitrate_read(addr, &val)) // Oh well
{
val = (uint8_t)nv3->nvbase.svga.vram[addr];
nv_log_verbose_only("Read byte from PRAMIN addr=0x%08x (raw address=0x%08x)\n", addr, raw_addr);
}
return (uint8_t)val;
}
// Read 16-bit ramin
uint16_t nv3_ramin_read16(uint32_t addr, void* priv)
{
if (!nv3) return 0x00;
addr &= (nv3->nvbase.svga.vram_max - 1);
// why does this not work in one line
svga_t* svga = &nv3->nvbase.svga;
uint16_t* vram_16bit = (uint16_t*)svga->vram;
uint32_t raw_addr = addr; // saved after and
addr ^= (nv3->nvbase.svga.vram_max - 0x10);
addr >>= 1; // what
uint32_t val = 0x00;
if (!nv3_ramin_arbitrate_read(addr, &val))
{
val = (uint16_t)vram_16bit[addr];
nv_log_verbose_only("Read word from PRAMIN addr=0x%08x (raw address=0x%08x)\n", addr, raw_addr);
}
return val;
}
// Read 32-bit ramin
uint32_t nv3_ramin_read32(uint32_t addr, void* priv)
{
if (!nv3)
return 0x00;
addr &= (nv3->nvbase.svga.vram_max - 1);
// why does this not work in one line
uint32_t* vram_32bit = (uint32_t*)nv3->nvbase.svga.vram;
uint32_t raw_addr = addr; // saved after and logged
addr ^= (nv3->nvbase.svga.vram_max - 0x10);
addr >>= 2; // what
uint32_t val = 0x00;
if (!nv3_ramin_arbitrate_read(addr, &val))
{
val = vram_32bit[addr];
nv_log_verbose_only("Read dword from PRAMIN 0x%08x <- 0x%08x (raw address=0x%08x)\n", val, addr, raw_addr);
}
return val;
}
// Write 8-bit ramin
void nv3_ramin_write8(uint32_t addr, uint8_t val, void* priv)
{
if (!nv3) return;
addr &= (nv3->nvbase.svga.vram_max - 1);
uint32_t raw_addr = addr; // saved after and
// Structures in RAMIN are stored from the bottom of vram up in reverse order
// this can be explained without bitwise math like so:
// real VRAM address = VRAM_size - (ramin_address - (ramin_address % reversal_unit_size)) - reversal_unit_size + (ramin_address % reversal_unit_size)
// reversal unit size in this case is 16 bytes, vram size is 2-8mb (but 8mb is zx/nv3t only and 2mb...i haven't found a 22mb card)
addr ^= (nv3->nvbase.svga.vram_max - 0x10);
uint32_t val32 = (uint32_t)val;
if (!nv3_ramin_arbitrate_write(addr, val32))
{
nv3->nvbase.svga.vram[addr] = val;
nv_log_verbose_only("Write byte to PRAMIN addr=0x%08x val=0x%02x (raw address=0x%08x)\n", addr, val, raw_addr);
}
}
// Write 16-bit ramin
void nv3_ramin_write16(uint32_t addr, uint16_t val, void* priv)
{
if (!nv3) return;
addr &= (nv3->nvbase.svga.vram_max - 1);
// why does this not work in one line
svga_t* svga = &nv3->nvbase.svga;
uint16_t* vram_16bit = (uint16_t*)svga->vram;
uint32_t raw_addr = addr; // saved after and
addr ^= (nv3->nvbase.svga.vram_max - 0x10);
addr >>= 1; // what
uint32_t val32 = (uint32_t)val;
if (!nv3_ramin_arbitrate_write(addr, val32))
{
vram_16bit[addr] = val;
nv_log_verbose_only("Write word to PRAMIN addr=0x%08x val=0x%04x (raw address=0x%08x)\n", addr, val, raw_addr);
}
}
// Write 32-bit ramin
void nv3_ramin_write32(uint32_t addr, uint32_t val, void* priv)
{
if (!nv3) return;
addr &= (nv3->nvbase.svga.vram_max - 1);
// why does this not work in one line
svga_t* svga = &nv3->nvbase.svga;
uint32_t* vram_32bit = (uint32_t*)svga->vram;
uint32_t raw_addr = addr; // saved after and
addr ^= (nv3->nvbase.svga.vram_max - 0x10);
addr >>= 2; // what
if (!nv3_ramin_arbitrate_write(addr, val))
{
vram_32bit[addr] = val;
nv_log_verbose_only("Write dword to PRAMIN addr=0x%08x val=0x%08x (raw address=0x%08x)\n", addr, val, raw_addr);
}
}
void nv3_pfifo_interrupt(uint32_t id, bool fire_now)
{
nv3->pfifo.interrupt_status |= (1 << id);
nv3_pmc_handle_interrupts(fire_now);
}
/*
RAMIN access arbitration functions
Arbitrates reads and writes to RAMFC (unused dma context storage), RAMRO (invalid object submission location), RAMHT (hashtable for graphics objectstorage) unused audio memory (RAMAU?)
and generic RAMIN
Takes a pointer to a result integer. This is because we need to check its result in our normal write function.
Returns true if a valid "non-generic" address was found (e.g. RAMFC/RAMRO/RAMHT). False if the specified address is a generic RAMIN address
*/
bool nv3_ramin_arbitrate_read(uint32_t address, uint32_t* value)
{
if (!nv3) return 0x00;
uint32_t ramht_size = ((nv3->pfifo.ramht_config >> NV3_PFIFO_CONFIG_RAMHT_SIZE) & 0x03);
uint32_t ramro_size = ((nv3->pfifo.ramro_config >> NV3_PFIFO_CONFIG_RAMRO_SIZE) & 0x01);
// Get the addresses of RAMHT, RAMFC, RAMRO
// They must be within first 64KB of PRAMIN!
uint32_t ramht_start = ((nv3->pfifo.ramht_config >> NV3_PFIFO_CONFIG_RAMHT_BASE_ADDRESS) & 0x0F) << 12; // Must be 0x1000 aligned
uint32_t ramfc_start = ((nv3->pfifo.ramfc_config >> NV3_PFIFO_CONFIG_RAMFC_BASE_ADDRESS) & 0x7F) << 9; // Must be 0x200 aligned
uint32_t ramro_start = ((nv3->pfifo.ramro_config >> NV3_PFIFO_CONFIG_RAMRO_BASE_ADDRESS) & 0x7F) << 9; // Must be 0x200 aligned
// Calculate the RAMHT and RAMRO end points.
// (RAMFC is always 0x1000 bytes on NV3.)
uint32_t ramht_end = ramht_start;
uint32_t ramfc_end = ramfc_start + 0x1000;
uint32_t ramro_end = ramro_start;
switch (ramht_size)
{
case NV3_PFIFO_CONFIG_RAMHT_SIZE_4K:
ramht_end = ramht_start + NV3_RAMIN_RAMHT_SIZE_0;
break;
case NV3_PFIFO_CONFIG_RAMHT_SIZE_8K:
ramht_end = ramht_start + NV3_RAMIN_RAMHT_SIZE_1;
break;
case NV3_PFIFO_CONFIG_RAMHT_SIZE_16K:
ramht_end = ramht_start + NV3_RAMIN_RAMHT_SIZE_2;
break;
case NV3_PFIFO_CONFIG_RAMHT_SIZE_32K:
ramht_end = ramht_start + NV3_RAMIN_RAMHT_SIZE_3;
break;
}
switch (ramro_size)
{
case NV3_PFIFO_CONFIG_RAMRO_SIZE_512B:
ramro_end = ramro_start + NV3_RAMIN_RAMRO_SIZE_0;
break;
case NV3_PFIFO_CONFIG_RAMRO_SIZE_8K:
ramro_end = ramro_start + NV3_RAMIN_RAMRO_SIZE_1;
break;
}
if (address >= ramht_start
&& address <= ramht_end)
{
*value = nv3_ramht_read(address);
return true;
}
else if (address >= ramfc_start
&& address <= ramfc_end)
{
*value = nv3_ramfc_read(address);
return true;
}
else if (address >= ramro_start
&& address <= ramro_end)
{
*value = nv3_ramro_read(address);
return true;
}
/* temp */
return false;
}
bool nv3_ramin_arbitrate_write(uint32_t address, uint32_t value)
{
if (!nv3) return 0x00;
uint32_t ramht_size = ((nv3->pfifo.ramht_config >> NV3_PFIFO_CONFIG_RAMHT_SIZE) & 0x03);
uint32_t ramro_size = ((nv3->pfifo.ramro_config >> NV3_PFIFO_CONFIG_RAMRO_SIZE) & 0x01);
// Get the addresses of RAMHT, RAMFC, RAMRO
// They must be within first 64KB of PRAMIN!
uint32_t ramht_start = ((nv3->pfifo.ramht_config >> NV3_PFIFO_CONFIG_RAMHT_BASE_ADDRESS) & 0x0F) << 12; // Must be 0x1000 aligned
uint32_t ramfc_start = ((nv3->pfifo.ramfc_config >> NV3_PFIFO_CONFIG_RAMFC_BASE_ADDRESS) & 0x7F) << 9; // Must be 0x200 aligned
uint32_t ramro_start = ((nv3->pfifo.ramro_config >> NV3_PFIFO_CONFIG_RAMRO_BASE_ADDRESS) & 0x7F) << 9; // Must be 0x200 aligned
// Calculate the RAMHT and RAMRO end points.
// (RAMFC is always 0x1000 bytes on NV3.)
uint32_t ramht_end = ramht_start;
uint32_t ramfc_end = ramfc_start + 0x1000;
uint32_t ramro_end = ramro_start;
switch (ramht_size)
{
case NV3_PFIFO_CONFIG_RAMHT_SIZE_4K:
ramht_end = ramht_start + NV3_RAMIN_RAMHT_SIZE_0;
break;
case NV3_PFIFO_CONFIG_RAMHT_SIZE_8K:
ramht_end = ramht_start + NV3_RAMIN_RAMHT_SIZE_1;
break;
case NV3_PFIFO_CONFIG_RAMHT_SIZE_16K:
ramht_end = ramht_start + NV3_RAMIN_RAMHT_SIZE_2;
break;
case NV3_PFIFO_CONFIG_RAMHT_SIZE_32K:
ramht_end = ramht_start + NV3_RAMIN_RAMHT_SIZE_3;
break;
}
switch (ramro_size)
{
case NV3_PFIFO_CONFIG_RAMRO_SIZE_512B:
ramro_end = ramro_start + NV3_RAMIN_RAMRO_SIZE_0;
break;
case NV3_PFIFO_CONFIG_RAMRO_SIZE_8K:
ramro_end = ramro_start + NV3_RAMIN_RAMRO_SIZE_1;
break;
}
// send the addresses to the right part
if (address >= ramht_start
&& address <= ramht_end)
{
nv3_ramht_write(address, value);
return true;
}
else if (address >= ramfc_start
&& address <= ramfc_end)
{
nv3_ramfc_write(address, value);
return true;
}
else if (address >= ramro_start
&& address <= ramro_end)
{
nv3_ramro_write(address, value);
return true;
}
return false;
}
// THIS IS THE MOST IMPORTANT FUNCTION!
bool nv3_ramin_find_object(uint32_t name, uint32_t cache_num, uint8_t channel, uint8_t subchannel)
{
// TODO: WRITE IT!!!
// Set the number of entries to search based on the ramht size (2*(size+1))
// Not a switch statement in case newer gpus have larger ramins
uint32_t bucket_entries = 2;
uint8_t ramht_size = (nv3->pfifo.ramht_config >> NV3_PFIFO_CONFIG_RAMHT_SIZE) & 0x03;
switch (ramht_size)
{
case NV3_PFIFO_CONFIG_RAMHT_SIZE_4K:
// stays as is
break;
case NV3_PFIFO_CONFIG_RAMHT_SIZE_8K:
bucket_entries = 4;
break;
case NV3_PFIFO_CONFIG_RAMHT_SIZE_16K:
bucket_entries = 8;
break;
case NV3_PFIFO_CONFIG_RAMHT_SIZE_32K:
bucket_entries = 16;
break;
}
// Calculate the address in the hashtable
uint32_t ramht_base = ((nv3->pfifo.ramht_config >> NV3_PFIFO_CONFIG_RAMHT_BASE_ADDRESS) & 0x0F) << NV3_PFIFO_CONFIG_RAMHT_BASE_ADDRESS;
// This is certainly wrong. But the objects seem to be written to 4600? So I just multiply it by 80 to multiply the final address by 10.
// Why does this work?
uint32_t ramht_cur_address = ramht_base + (nv3_ramht_hash(name, channel) * bucket_entries * 8);
nv_log_verbose_only("Beginning search for graphics object at RAMHT base=0x%04x, name=0x%08x, Cache%d, channel=%d.%d)\n",
ramht_cur_address, name, cache_num, channel, subchannel);
bool found_object = false;
// set up some variables
uint32_t found_obj_name = 0x00;
nv3_ramin_context_t obj_context_struct = {0};
for (uint32_t bucket_entry = 0; bucket_entry < bucket_entries; bucket_entry++)
{
found_obj_name = nv3_ramin_read32(ramht_cur_address, NULL);
ramht_cur_address += 0x04;
uint32_t obj_context = nv3_ramin_read32(ramht_cur_address, NULL);
ramht_cur_address += 0x04;
obj_context_struct = *(nv3_ramin_context_t*)&obj_context;
// see if the object is in the right channel
if (found_obj_name == name
&& obj_context_struct.channel == channel)
{
found_object = true;
break;
}
}
if (!found_object)
{
if (!cache_num)
{
nv3->pfifo.debug_0 |= NV3_PFIFO_CACHE0_ERROR_PENDING;
nv3->pfifo.cache0_settings.pull0 |= NV3_PFIFO_CACHE0_PULL0_HASH_FAILURE;
//It turns itself off on failure, the drivers turn it back on
nv3->pfifo.cache0_settings.pull0 &= ~NV3_PFIFO_CACHE0_PULL0_ENABLED;
}
else
{
nv3->pfifo.debug_0 |= NV3_PFIFO_CACHE1_ERROR_PENDING;
nv3->pfifo.cache1_settings.pull0 |= NV3_PFIFO_CACHE1_PULL0_HASH_FAILURE;
//It turns itself off on failure, the drivers turn it back on
nv3->pfifo.cache1_settings.pull0 &= ~NV3_PFIFO_CACHE1_PULL0_ENABLED;
}
nv3_pfifo_interrupt(NV3_PFIFO_INTR_CACHE_ERROR, true);
return false;
}
// So we did find an object.
// Now try to read some of this...
// Class ID is 5 bits in all other parts of the gpu but 7 bits here. A move in a direction that didn't pan out?
// Represented as 0x40-0x5f? Some other meaning
// Perform more validation
if (obj_context_struct.class_id < NV3_PFIFO_FIRST_VALID_GRAPHICS_OBJECT_ID
|| obj_context_struct.class_id > NV3_PFIFO_LAST_VALID_GRAPHICS_OBJECT_ID)
{
fatal("NV3: Invalid graphics object class ID name=0x%04x type=%04x, interpreted by pgraph as: %04x (Contact starfrost)",
name, obj_context_struct.class_id, obj_context_struct.class_id & 0x1F);
}
else if (obj_context_struct.channel > (NV3_DMA_CHANNELS - 1))
fatal("NV3: Super fucked up graphics object. Contact starfrost with the error string: DMA Channel ID=%d, it should be 0-7", obj_context_struct.channel);
// Illegal accesses sent to RAMRO, so ignore here
// TODO: SEND THESE TO RAMRO!!!!!
#ifndef RELEASE_BUILD
nv3_debug_ramin_print_context_info(name, obj_context_struct);
#endif
// By definition we can't have a cache error by here so take it off
if (!cache_num)
nv3->pfifo.cache0_settings.pull0 &= ~NV3_PFIFO_CACHE0_PULL0_HASH_FAILURE;
else
nv3->pfifo.cache1_settings.pull0 &= ~NV3_PFIFO_CACHE1_PULL0_HASH_FAILURE;
// Caches store all the subchannels for our current dma channel and basically get stale every context switch
// Also we have to check that a osftware object didn't end up in here...
bool is_software = false;
if (!cache_num)
is_software = (nv3->pfifo.cache0_settings.context[subchannel] & 0x800000);
else
is_software = (nv3->pfifo.cache1_settings.context[subchannel] & 0x800000);
// This isn't an error but it's sent as an interrupt so the drivers can sync
if (is_software)
{
// handle it as an error
if (!cache_num)
{
nv3->pfifo.cache0_settings.pull0 |= NV3_PFIFO_CACHE0_PULL0_SOFTWARE_METHOD;
nv3->pfifo.cache0_settings.pull0 &= ~NV3_PFIFO_CACHE0_PULL0_ENABLED;
}
else
{
nv3->pfifo.cache1_settings.pull0 |= NV3_PFIFO_CACHE1_PULL0_SOFTWARE_METHOD;
nv3->pfifo.cache1_settings.pull0 &= ~NV3_PFIFO_CACHE1_PULL0_ENABLED;
}
// It's an error but it isn't lol
nv3_pfifo_interrupt(NV3_PFIFO_INTR_CACHE_ERROR, true);
}
else
{
// obviously turn off the "is software" if it's not
if (!cache_num)
nv3->pfifo.cache0_settings.pull0 &= ~NV3_PFIFO_CACHE0_PULL0_SOFTWARE_METHOD;
else
nv3->pfifo.cache1_settings.pull0 &= ~NV3_PFIFO_CACHE1_PULL0_SOFTWARE_METHOD;
}
// Ok we found it. Lol
return true;
}
// Prints out some informaiton about the object
void nv3_debug_ramin_print_context_info(uint32_t name, nv3_ramin_context_t context)
{
#ifndef RELEASE_BUILD
nv_log_verbose_only("Found object:\n");
nv_log_verbose_only("Param: 0x%04x\n", name);
nv_log_verbose_only("Context:\n");
nv_log_verbose_only("DMA Channel %d (0-7 valid)\n", context.channel);
nv_log_verbose_only("Class ID: 0x%04x (%s)\n", context.class_id & 0x1F, nv3_class_names[context.class_id & 0x1F]);
nv_log_verbose_only("Render Engine %d (0=Software, also DMA? 1=Accelerated Renderer)\n", context.is_rendering);
nv_log_verbose_only("PRAMIN Offset 0x%08x\n", context.ramin_offset << 4);
#endif
}

View File

@@ -0,0 +1,40 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* NV3 PFIFO RAMFC area: Stores context for unused DMA channels
*
*
*
* Authors: Connor Hyde, <mario64crashed@gmail.com> I need a better email address ;^)
*
* Copyright 2024-2025 starfrost
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/pci.h>
#include <86box/rom.h> // DEPENDENT!!!
#include <86box/video.h>
#include <86box/nv/vid_nv.h>
#include <86box/nv/vid_nv3.h>
uint32_t nv3_ramfc_read(uint32_t address)
{
nv_log_verbose_only("RAMFC (Unused DMA channel context) Read (0x%04x) (UNIMPLEMENTED returning 0x00)\n", address);
return 0x00; //temp
}
void nv3_ramfc_write(uint32_t address, uint32_t value)
{
nv_log_verbose_only("RAMFC (Unused DMA channel context) Write (0x%04x -> 0x%04x)\n", value, address);
}

View File

@@ -0,0 +1,56 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* NV3 PFIFO hashtable (Quickly access submitted DMA objects)
*
*
*
* Authors: Connor Hyde, <mario64crashed@gmail.com> I need a better email address ;^)
*
* Copyright 2024-2025 starfrost
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/pci.h>
#include <86box/rom.h> // DEPENDENT!!!
#include <86box/video.h>
#include <86box/nv/vid_nv.h>
#include <86box/nv/vid_nv3.h>
/* This implements the hash that all the objects are stored within.
It is used to get the offset within RAMHT of a graphics object.
*/
uint32_t nv3_ramht_hash(uint32_t name, uint32_t channel)
{
// the official nvidia hash algorithm, tweaked for readability
uint32_t hash = ((name ^ (name >> 8) ^ (name >> 16) ^ (name >> 24)) & 0xFF) ^ (channel & NV3_DMA_CHANNELS_TOTAL);
// is this the right endianness?
nv_log_verbose_only("Generated RAMHT hash 0x%04x (RAMHT slot=0x%04x (from name 0x%08x for DMA channel 0x%04x)\n)\n", hash, (hash/8), name, channel);
return hash;
}
uint32_t nv3_ramht_read(uint32_t address)
{
nv_log_verbose_only("RAMHT (Graphics object storage hashtable) Read (0x%04x), I DON'T BELIEVE THIS SHOULD EVER HAPPEN - RETURNING 0x00\n", address);
return 0x00;
}
void nv3_ramht_write(uint32_t address, uint32_t value)
{
nv_log_verbose_only("RAMHT (Graphics object storage hashtable) Write (0x%04x -> 0x%04x), I DON'T BELIEVE THIS SHOULD EVER HAPPEN - UNIMPLEMENTED\n", value, address);
}

View File

@@ -0,0 +1,40 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* NV3 PFIFO ram runout area (you fucked up)
*
*
*
* Authors: Connor Hyde, <mario64crashed@gmail.com> I need a better email address ;^)
*
* Copyright 2024-2025 starfrost
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/pci.h>
#include <86box/rom.h> // DEPENDENT!!!
#include <86box/video.h>
#include <86box/nv/vid_nv.h>
#include <86box/nv/vid_nv3.h>
uint32_t nv3_ramro_read(uint32_t address)
{
nv_log("BIG Problem: RAM Runout (invalid dma object submission) Read (0x%04x)\n", address);
return 0x00;
}
void nv3_ramro_write(uint32_t address, uint32_t value)
{
nv_log("BIG Problem: RAM Runout WRITE, OH CRAP!!!! (0x%04x -> 0x%04x)", value, address);
}

View File

@@ -0,0 +1,227 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* NV3 PTIMER - PIT emulation
*
*
*
* Authors: Connor Hyde, <mario64crashed@gmail.com> I need a better email address ;^)
*
* Copyright 2024-2025 starfrost
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/pci.h>
#include <86box/rom.h> // DEPENDENT!!!
#include <86box/video.h>
#include <86box/nv/vid_nv.h>
#include <86box/nv/vid_nv3.h>
nv_register_t ptimer_registers[] = {
{ NV3_PTIMER_INTR, "PTIMER - Interrupt Status", NULL, NULL},
{ NV3_PTIMER_INTR_EN, "PTIMER - Interrupt Enable", NULL, NULL,},
{ NV3_PTIMER_NUMERATOR, "PTIMER - Numerator", NULL, NULL, },
{ NV3_PTIMER_DENOMINATOR, "PTIMER - Denominator", NULL, NULL, },
{ NV3_PTIMER_TIME_0_NSEC, "PTIMER - Time0", NULL, NULL, },
{ NV3_PTIMER_TIME_1_NSEC, "PTIMER - Time1", NULL, NULL, },
{ NV3_PTIMER_ALARM_NSEC, "PTIMER - Alarm", NULL, NULL, },
{ NV_REG_LIST_END, NULL, NULL, NULL}, // sentinel value
};
// ptimer init code
void nv3_ptimer_init(void)
{
nv_log("Initialising PTIMER...");
nv_log("Done!\n");
}
// Handles the PTIMER alarm interrupt
void nv3_ptimer_interrupt(uint32_t num)
{
nv3->ptimer.interrupt_status |= (1 << num);
nv3_pmc_handle_interrupts(true);
}
// Ticks the timer.
void nv3_ptimer_tick(double real_time)
{
// prevent a divide by zero
if (nv3->ptimer.clock_numerator == 0
|| nv3->ptimer.clock_denominator == 0)
return;
// get the current time
// See Envytools. We need to use the frequency as a source.
// We need to figure out how many cycles actually occurred because this counts up every cycle...
// However it seems that their formula is wrong. I can't be bothered to figure out what's going on and, based on documentation from NVIDIA,
// timer_0 is meant to roll over every 4 seconds. Multiplying by 10 basically does the job.
// Convert to microseconds
double freq_base = (real_time / 1000000.0f) / ((double)1.0 / nv3->nvbase.memory_clock_frequency) * 10.0f;
double current_time = freq_base * ((double)nv3->ptimer.clock_numerator) / (double)nv3->ptimer.clock_denominator; // *10.0?
// truncate it
nv3->ptimer.time += (uint64_t)current_time;
// Check if the alarm has actually triggered..
// Only log on ptimer alarm. Otherwise, it's too much spam.
if (nv3->ptimer.time >= nv3->ptimer.alarm)
{
nv_log_verbose_only("PTIMER alarm interrupt fired (if interrupts enabled) because we reached TIME value 0x%08x\n", nv3->ptimer.alarm);
nv3_ptimer_interrupt(NV3_PTIMER_INTR_ALARM);
}
}
uint32_t nv3_ptimer_read(uint32_t address)
{
// always enabled
nv_register_t* reg = nv_get_register(address, ptimer_registers, sizeof(ptimer_registers)/sizeof(ptimer_registers[0]));
// Only log these when tehy actually tick
if (address != NV3_PTIMER_TIME_0_NSEC
&& address != NV3_PTIMER_TIME_1_NSEC)
{
nv_log_verbose_only("PTIMER Read from 0x%08x", address);
}
uint32_t ret = 0x00;
// if the register actually exists
if (reg)
{
// on-read function
if (reg->on_read)
ret = reg->on_read();
else
{
// Interrupt state:
// Bit 0: Alarm
switch (reg->address)
{
case NV3_PTIMER_INTR:
ret = nv3->ptimer.interrupt_status;
break;
case NV3_PTIMER_INTR_EN:
ret = nv3->ptimer.interrupt_enable;
break;
case NV3_PTIMER_NUMERATOR:
ret = nv3->ptimer.clock_numerator; // 15:0
break;
case NV3_PTIMER_DENOMINATOR:
ret = nv3->ptimer.clock_denominator ; //15:0
break;
// 64-bit value
// High part
case NV3_PTIMER_TIME_0_NSEC:
ret = nv3->ptimer.time & 0xFFFFFFFF; //28:0
break;
// Low part
case NV3_PTIMER_TIME_1_NSEC:
ret = nv3->ptimer.time >> 32; // 31:5
break;
case NV3_PTIMER_ALARM_NSEC:
ret = nv3->ptimer.alarm; // 31:5
break;
}
}
//TIME0 and TIME1 produce too much log spam that slows everything down
if (reg->address != NV3_PTIMER_TIME_0_NSEC
&& reg->address != NV3_PTIMER_TIME_1_NSEC)
{
if (reg->friendly_name)
nv_log_verbose_only(": 0x%08x <- %s\n", ret, reg->friendly_name);
else
nv_log_verbose_only("\n");
}
}
else
{
nv_log(": Unknown register read (address=0x%08x), returning 0x00\n", address);
}
return ret;
}
void nv3_ptimer_write(uint32_t address, uint32_t value)
{
// before doing anything, check the subsystem enablement
nv_register_t* reg = nv_get_register(address, ptimer_registers, sizeof(ptimer_registers)/sizeof(ptimer_registers[0]));
nv_log_verbose_only("PTIMER Write 0x%08x -> 0x%08x", value, address);
// if the register actually exists
if (reg)
{
if (reg->friendly_name)
nv_log_verbose_only(": %s\n", reg->friendly_name);
else
nv_log_verbose_only("\n");
// on-read function
if (reg->on_write)
reg->on_write(value);
else
{
switch (reg->address)
{
// Interrupt state:
// Bit 0 - Alarm
case NV3_PTIMER_INTR:
nv3->ptimer.interrupt_status &= ~value;
nv3_pmc_clear_interrupts();
break;
// Interrupt enablement state
case NV3_PTIMER_INTR_EN:
nv3->ptimer.interrupt_enable = value & 0x1;
break;
// nUMERATOR
case NV3_PTIMER_NUMERATOR:
nv3->ptimer.clock_numerator = value & 0xFFFF; // 15:0
break;
case NV3_PTIMER_DENOMINATOR:
// prevent Div0
if (!value)
value = 1;
nv3->ptimer.clock_denominator = value & 0xFFFF; //15:0
break;
// 64-bit value
// High part
case NV3_PTIMER_TIME_0_NSEC:
nv3->ptimer.time |= (value) & 0xFFFFFFE0; //28:0
break;
// Low part
case NV3_PTIMER_TIME_1_NSEC:
nv3->ptimer.time |= ((uint64_t)(value & 0xFFFFFFE0) << 32); // 31:5
break;
case NV3_PTIMER_ALARM_NSEC:
nv3->ptimer.alarm = value & 0xFFFFFFE0; // 31:5
break;
}
}
}
else /* Completely unknown */
{
nv_log(": Unknown register write (address=0x%08x)\n", address);
}
}

View File

@@ -0,0 +1,159 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* NV3 PVIDEO - Video Overlay
*
*
*
* Authors: Connor Hyde, <mario64crashed@gmail.com> I need a better email address ;^)
*
* Copyright 2024-2025 starfrost
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/pci.h>
#include <86box/rom.h> // DEPENDENT!!!
#include <86box/video.h>
#include <86box/nv/vid_nv.h>
#include <86box/nv/vid_nv3.h>
nv_register_t pvideo_registers[] = {
{ NV3_PVIDEO_INTR, "PVIDEO - Interrupt Status", NULL, NULL},
{ NV3_PVIDEO_INTR_EN, "PVIDEO - Interrupt Enable", NULL, NULL,},
{ NV3_PVIDEO_FIFO_THRESHOLD, "PVIDEO - FIFO Fill Threshold", NULL, NULL},
{ NV3_PVIDEO_FIFO_BURST_LENGTH, "PVIDEO - FIFO Burst Length (1=32, 2=64, 3=128)", NULL, NULL},
{ NV3_PVIDEO_OVERLAY, "PVIDEO - Overlay Info (Bit0 = Video On, Bit4 = Key On, Bit8 = Format, 0=CCIR, 1=YUV2)", NULL, NULL },
{ NV_REG_LIST_END, NULL, NULL, NULL}, // sentinel value
};
// ptimer init code
void nv3_pvideo_init(void)
{
nv_log("Initialising PVIDEO...");
nv_log("Done!\n");
}
uint32_t nv3_pvideo_read(uint32_t address)
{
// before doing anything, check the subsystem enablement
nv_register_t* reg = nv_get_register(address, pvideo_registers, sizeof(pvideo_registers)/sizeof(pvideo_registers[0]));
uint32_t ret = 0x00;
// todo: friendly logging
nv_log_verbose_only("PVIDEO Read from 0x%08x", address);
// if the register actually exists
if (reg)
{
if (reg->friendly_name)
nv_log_verbose_only(": %s\n", reg->friendly_name);
else
nv_log_verbose_only("\n");
// on-read function
if (reg->on_read)
ret = reg->on_read();
else
{
// Interrupt state:
// Bit 0 - Notifier
switch (reg->address)
{
case NV3_PVIDEO_INTR:
ret = nv3->pvideo.interrupt_status;
break;
case NV3_PVIDEO_INTR_EN:
ret = nv3->pvideo.interrupt_enable;
break;
case NV3_PVIDEO_FIFO_THRESHOLD:
ret = nv3->pvideo.fifo_threshold;
break;
case NV3_PVIDEO_FIFO_BURST_LENGTH:
ret = nv3->pvideo.fifo_burst_size & 0x03;
break;
case NV3_PVIDEO_OVERLAY:
ret = nv3->pvideo.overlay_settings & 0xFF;
break;
}
}
if (reg->friendly_name)
nv_log_verbose_only(": 0x%08x <- %s\n", ret, reg->friendly_name);
else
nv_log_verbose_only("\n");
}
else
{
nv_log(": Unknown register read (address=0x%08x), returning 0x00\n", address);
}
return ret;
}
void nv3_pvideo_write(uint32_t address, uint32_t value)
{
// before doing anything, check the subsystem enablement
nv_register_t* reg = nv_get_register(address, pvideo_registers, sizeof(pvideo_registers)/sizeof(pvideo_registers[0]));
nv_log_verbose_only("PVIDEO Write 0x%08x -> 0x%08x\n", value, address);
// if the register actually exists
if (reg)
{
if (reg->friendly_name)
nv_log_verbose_only(": %s\n", reg->friendly_name);
else
nv_log_verbose_only("\n");
// on-read function
if (reg->on_write)
reg->on_write(value);
else
{
switch (reg->address)
{
// Interrupt state:
// Bit 0 - Notifier
case NV3_PVIDEO_INTR:
nv3->pvideo.interrupt_status &= ~value;
nv3_pmc_clear_interrupts();
break;
case NV3_PVIDEO_INTR_EN:
nv3->pvideo.interrupt_enable = value & 0x00000001;
break;
case NV3_PVIDEO_FIFO_THRESHOLD:
// only bits 6:3 matter
nv3->pvideo.fifo_threshold = ((value >> 3) & 0x0F) << 3;
break;
case NV3_PVIDEO_FIFO_BURST_LENGTH:
nv3->pvideo.fifo_burst_size = value & 0x03;
break;
case NV3_PVIDEO_OVERLAY:
nv3->pvideo.overlay_settings = value & 0xFF;
break;
}
}
}
else /* Completely unknown */
{
nv_log(": Unknown register write (address=0x%08x)\n", address);
}
}

View File

@@ -0,0 +1,70 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* NV3 User Submission Area (NV_USER, conceptually considered "Cache1 Pusher")
*
*
*
* Authors: Connor Hyde, <mario64crashed@gmail.com> I need a better email address ;^)
*
* Copyright 2024-2025 starfrost
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/pci.h>
#include <86box/rom.h> // DEPENDENT!!!
#include <86box/video.h>
#include <86box/nv/vid_nv.h>
#include <86box/nv/vid_nv3.h>
// PIO Method Submission
// 128 channels conceptually supported - a hangover from nv1 where multiple windows all directly programming the gpu were supported? total lunacy.
uint32_t nv3_user_read(uint32_t address)
{
// Get the address within the subchannel
//todo: print out the subchannel
uint8_t method_offset = (address & 0x1FFC);
uint8_t channel = (address - NV3_USER_START) / 0x10000;
uint8_t subchannel = ((address - NV3_USER_START)) / 0x2000 % NV3_DMA_SUBCHANNELS_PER_CHANNEL;
nv_log_verbose_only("User Submission Area PIO Channel %d.%d method_offset=0x%04x\n", channel, subchannel, method_offset);
// 0x10 is free CACHE1 object
// TODO: THERE ARE OTHER STUFF!
switch (method_offset)
{
case NV3_SUBCHANNEL_PIO_IS_PFIFO_FREE:
return nv3_pfifo_cache1_num_free_spaces();
case NV3_SUBCHANNEL_PIO_ALWAYS_ZERO_START ... NV3_SUBCHANNEL_PIO_ALWAYS_ZERO_END:
return 0x00;
}
nv_log("NV_USER READ: Channel FIELD NOT IMPLEMENTED!!!! offset=0x%04x\n", method_offset);
return 0x00;
};
// Although NV3 doesn't have DMA mode unlike NV4 and later, it's conceptually similar to a "pusher" that pushes graphics commands that you write into CACHE1 that are then pulled out.
// So we send the writes here. This might do other stuff, so we keep this function
void nv3_user_write(uint32_t address, uint32_t value)
{
nv3_pfifo_cache1_push(address, value);
// This isn't ideal, but otherwise, the dynarec causes the GPU to write so many objects into CACHE1, it starts overwriting the old objects
// This basically makes the fifo not a fifo, but oh well
nv3_pfifo_cache1_pull();
}

388
src/video/nv/nv4/nv4_core.c Normal file
View File

@@ -0,0 +1,388 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* NV4 bringup and device emulation.
*
*
* Authors: Connor Hyde, <mario64crashed@gmail.com> I need a better email address ;^)
*
* Copyright 2024-2025 starfrost
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/io.h>
#include <86box/pci.h>
#include <86box/rom.h> // DEPENDENT!!!
#include <86box/video.h>
#include <86box/nv/vid_nv.h>
#include <86box/nv/vid_nv4.h>
nv4_t* nv4;
// Stolen from Voodoo 3
static video_timings_t timing_nv4_agp = { .type = VIDEO_AGP, .write_b = 2, .write_w = 2, .write_l = 1, .read_b = 20, .read_w = 20, .read_l = 21 };
// Initialise the MMIO mappings
void nv4_init_mappings_mmio(void)
{
nv_log("Initialising MMIO mapping\n");
// 0x0 - 1000000: regs
// 0x1000000-2000000
// initialize the mmio mapping
mem_mapping_add(&nv4->nvbase.mmio_mapping, 0, 0,
nv4_mmio_read8,
nv4_mmio_read16,
nv4_mmio_read32,
nv4_mmio_write8,
nv4_mmio_write16,
nv4_mmio_write32,
NULL, MEM_MAPPING_EXTERNAL, nv4);
// initialize the mmio mapping
mem_mapping_add(&nv4->nvbase.ramin_mapping, 0, 0,
nv4_ramin_read8,
nv4_ramin_read16,
nv4_ramin_read32,
nv4_ramin_write8,
nv4_ramin_write16,
nv4_ramin_write32,
NULL, MEM_MAPPING_EXTERNAL, nv4);
}
void nv4_init_mappings_svga(void)
{
nv_log("Initialising SVGA core memory mapping\n");
// setup the svga mappings
mem_mapping_add(&nv4->nvbase.framebuffer_mapping, 0, 0,
nv4_dfb_read8,
nv4_dfb_read16,
nv4_dfb_read32,
nv4_dfb_write8,
nv4_dfb_write16,
nv4_dfb_write32,
nv4->nvbase.svga.vram, 0, &nv4->nvbase.svga);
// the SVGA/LFB mapping is also mirrored
mem_mapping_add(&nv4->nvbase.framebuffer_mapping_mirror, 0, 0,
nv4_dfb_read8,
nv4_dfb_read16,
nv4_dfb_read32,
nv4_dfb_write8,
nv4_dfb_write16,
nv4_dfb_write32,
nv4->nvbase.svga.vram, 0, &nv4->nvbase.svga);
io_sethandler(NV4_CIO_START, NV4_CIO_SIZE,
nv4_svga_read, NULL, NULL,
nv4_svga_write, NULL, NULL,
nv4);
}
void nv4_init_mappings(void)
{
nv4_init_mappings_mmio();
nv4_init_mappings_svga();
}
// Updates the mappings after initialisation.
void nv4_update_mappings(void)
{
// sanity check
if (!nv4)
return;
// setting this to 0 doesn't seem to disable it, based on the datasheet
nv_log("\nMemory Mapping Config Change:\n");
(nv4->nvbase.pci_config.pci_regs[PCI_REG_COMMAND] & PCI_COMMAND_IO) ? nv_log("Enable I/O\n") : nv_log("Disable I/O\n");
io_removehandler(NV4_CIO_START, NV4_CIO_SIZE,
nv4_svga_read, NULL, NULL,
nv4_svga_write, NULL, NULL,
nv4);
if (nv4->nvbase.pci_config.pci_regs[PCI_REG_COMMAND] & PCI_COMMAND_IO)
io_sethandler(NV4_CIO_START, NV4_CIO_SIZE,
nv4_svga_read, NULL, NULL,
nv4_svga_write, NULL, NULL,
nv4);
if (!(nv4->nvbase.pci_config.pci_regs[PCI_REG_COMMAND] & PCI_COMMAND_MEM))
{
nv_log("The memory was turned off, not much is going to happen.\n");
return;
}
// turn off bar0 and bar1 by defualt
mem_mapping_disable(&nv4->nvbase.mmio_mapping);
mem_mapping_disable(&nv4->nvbase.framebuffer_mapping);
mem_mapping_disable(&nv4->nvbase.framebuffer_mapping_mirror);
mem_mapping_disable(&nv4->nvbase.ramin_mapping);
// Setup BAR0 (MMIO)
nv_log("BAR0 (MMIO Base) = 0x%08x\n", nv4->nvbase.bar0_mmio_base);
if (nv4->nvbase.bar0_mmio_base)
{
mem_mapping_set_addr(&nv4->nvbase.mmio_mapping, nv4->nvbase.bar0_mmio_base, NV4_MMIO_SIZE);
mem_mapping_set_addr(&nv4->nvbase.ramin_mapping, nv4->nvbase.bar0_mmio_base + NV4_PRAMIN_START, NV4_PRAMIN_SIZE);
}
// if this breaks anything, remove it
nv_log("BAR1 (Linear Framebuffer & VRAM) = 0x%08x\n", nv4->nvbase.bar1_lfb_base);
if (nv4->nvbase.bar1_lfb_base)
{
if (nv4->nvbase.vram_amount == NV4_VRAM_SIZE_16MB)
{
// we don't need this one in the case of 16mb,
mem_mapping_disable(&nv4->nvbase.framebuffer_mapping_mirror);
mem_mapping_set_addr(&nv4->nvbase.framebuffer_mapping, nv4->nvbase.bar1_lfb_base, NV4_VRAM_SIZE_16MB);
}
else if (nv4->nvbase.vram_amount == NV4_VRAM_SIZE_8MB)
{
mem_mapping_set_addr(&nv4->nvbase.framebuffer_mapping, nv4->nvbase.bar1_lfb_base, NV4_VRAM_SIZE_8MB);
mem_mapping_set_addr(&nv4->nvbase.framebuffer_mapping_mirror, nv4->nvbase.bar1_lfb_base + NV4_VRAM_SIZE_8MB, NV4_VRAM_SIZE_8MB);
}
}
// Did we change the banked SVGA mode?
switch (nv4->nvbase.svga.gdcreg[NV4_PRMVIO_GX_MISC_INDEX] & 0x0c)
{
case NV4_PRMVIO_GX_MISC_BANKED_128K_A0000:
nv_log("SVGA Banked Mode = 128K @ A0000h\n");
mem_mapping_set_addr(&nv4->nvbase.svga.mapping, 0xA0000, 0x20000); // 128kb @ 0xA0000
nv4->nvbase.svga.banked_mask = 0x1FFFF;
break;
case NV4_PRMVIO_GX_MISC_BANKED_64K_A0000:
nv_log("SVGA Banked Mode = 64K @ A0000h\n");
mem_mapping_set_addr(&nv4->nvbase.svga.mapping, 0xA0000, 0x10000); // 64kb @ 0xA0000
nv4->nvbase.svga.banked_mask = 0xFFFF;
break;
case NV4_PRMVIO_GX_MISC_BANKED_32K_B0000:
nv_log("SVGA Banked Mode = 32K @ B0000h\n");
mem_mapping_set_addr(&nv4->nvbase.svga.mapping, 0xB0000, 0x8000); // 32kb @ 0xB0000
nv4->nvbase.svga.banked_mask = 0x7FFF;
break;
case NV4_PRMVIO_GX_MISC_BANKED_32K_B8000:
nv_log("SVGA Banked Mode = 32K @ B8000h\n");
mem_mapping_set_addr(&nv4->nvbase.svga.mapping, 0xB8000, 0x8000); // 32kb @ 0xB8000
nv4->nvbase.svga.banked_mask = 0x7FFF;
break;
}
}
bool nv4_init()
{
nv4 = calloc(1, sizeof(nv4_t));
if (!nv4->nvbase.vram_amount)
nv4->nvbase.vram_amount = device_get_config_int("vram_size");
/* Set log device name based on card model */
const char* log_device_name = "NV4";
/* Just hardcode full logging */
if (device_get_config_int("nv_debug_fulllog"))
nv4->nvbase.log = log_open(log_device_name);
else
nv4->nvbase.log = log_open_cyclic(log_device_name);
nv_log_set_device(nv4->nvbase.log);
nv4->nvbase.bus_generation = nv_bus_agp_2x;
// Figure out which vbios the user selected
// This depends on the bus we are using and if the gpu is rev a/b or rev c
const char* vbios_file = NV4_VBIOS_STB_REVA;
int32_t err = rom_init(&nv4->nvbase.vbios, vbios_file, 0xC0000, 0x8000, 0x7fff, 0, MEM_MAPPING_EXTERNAL);
if (err)
{
nv_log("NV4 FATAL: failed to load VBIOS err=%d\n", err);
fatal("Nvidia NV4 init failed: Somehow selected a nonexistent VBIOS? err=%d\n", err);
return false;
}
else
nv_log("Successfully loaded VBIOS located at %s\n", vbios_file);
pci_add_card(PCI_ADD_AGP, nv4_pci_read, nv4_pci_write, NULL, &nv4->nvbase.pci_slot);
svga_init(&nv4_device_agp, &nv4->nvbase.svga, nv4, nv4->nvbase.vram_amount,
nv4_recalc_timings, nv4_svga_read, nv4_svga_write, nv4_draw_cursor, NULL);
video_inform(VIDEO_FLAG_TYPE_SPECIAL, &timing_nv4_agp);
// Setup clock information. These values don't matter, I pulled them out of an STB BIOS, we don't need to macro them
nv4->pramdac.nvclk = 0x17809;
nv4->pramdac.mclk = 0x1a30a;
nv4->pramdac.vclk = 0x1400c;
//timer_add(nv4->pramdac.nvclk, )
nv4_init_mappings();
//nv4_update_mappings();
return true;
}
void* nv4_init_stb4400(const device_t *info)
{
bool successful = nv4_init();
if (successful)
return nv4;
else
return NULL;
}
void nv4_nvclk_tick()
{
}
void nv4_mclk_tick()
{
}
void nv4_vclk_tick()
{
}
void nv4_close(void* priv)
{
free(nv4);
}
void nv4_speed_changed(void *priv)
{
}
void nv4_draw_cursor(svga_t* svga, int32_t drawline)
{
}
//
// SVGA functions
//
void nv4_recalc_timings(svga_t* svga)
{
// sanity check
if (!nv4)
return;
nv4_t* nv4 = (nv4_t*)svga->priv;
// TODO: Everything, this code sucks, incl. NV4_PRAMDAC_GENERAL_CONTROL_BPC and the offset register
uint32_t pixel_mode = svga->crtc[NV4_CIO_CRE_PIXEL_INDEX] & 0x03;
svga->memaddr_latch += (svga->crtc[NV4_CIO_CRE_RPC0_INDEX] & 0x1F) << 16;
/* Turn off override if we are in VGA mode */
svga->override = !(pixel_mode == NV4_CIO_CRE_PIXEL_FORMAT_VGA);
/* NOTE: The RIVA 128 draws in a way almost completely separate to any other 86Box GPU.
Basically, we only blit to buffer32 when something changes and we don't even bother using a timer. We only render when there is something to actually render.
This is because there is no linear relationship between the contents of VRAM and the contents of the display which 86box's SVGA subsystem cannot tolerate.
In fact, the position in VRAM and pitch can be changed at any time via an NV_IMAGE_IN_MEMORY object.
Therefore, we need to completely bypass it using svga->override and draw our own rendering functions. This allows us to use a neat optimisation trick
to only ever actually draw when we need to do something. This shouldn't be a problem in games, because the drivers will read the current refresh rate from
the Windows settings, and then, just submit objects at that pace for anything that changes on the screen.
*/
// Set the pixel mode
switch (pixel_mode)
{
case NV4_CIO_CRE_PIXEL_FORMAT_8BPP:
svga->rowoffset += (svga->crtc[NV4_CIO_CRE_RPC0_INDEX] & 0xE0) << 1; // ?????
svga->bpp = 8;
svga->lowres = 0;
svga->map8 = svga->pallook;
break;
case NV4_CIO_CRE_PIXEL_FORMAT_16BPP:
/* This is some sketchy shit that is an attempt at an educated guess
at pixel clock differences between 9x and NT only in 16bpp. If there is ever an error on 9x with "interlaced" looking graphics,
this is what's causing it. Possibly fucking up the drivers under *ReactOS* of all things */
if ((svga->crtc[NV4_CIO_CR_VRS_INDEX] >> 1) & 0x01)
svga->rowoffset += (svga->crtc[NV4_CIO_CRE_RPC0_INDEX] & 0xE0) << 2;
else
svga->rowoffset += (svga->crtc[NV4_CIO_CRE_RPC0_INDEX] & 0xE0) << 3;
// 15bpp mode is removed on NV4
// TODO: Not svga
svga->bpp = 16;
svga->lowres = 0;
break;
case NV4_CIO_CRE_PIXEL_FORMAT_32BPP:
svga->rowoffset += (svga->crtc[NV4_CIO_CRE_RPC0_INDEX] & 0xE0) << 3;
svga->bpp = 32;
svga->lowres = 0;
//svga->render = nv4_render_32bpp;
break;
}
if (((svga->miscout >> 2) & 2) == 2)
{
// set clocks
//nv4_pramdac_set_pixel_clock();
//nv4_pramdac_set_vram_clock();
}
}
// See if the bios rom is available.
int32_t nv4_available(void)
{
return (rom_present(NV4_VBIOS_STB_REVA));
}
// NV4 (RIVA 128)
// AGP
// 8MB or 16MB VRAM
const device_t nv4_device_agp =
{
.name = "nVIDIA RIVA TNT (STB Velocity 4400)",
.internal_name = "nv4_stb4400",
.flags = DEVICE_AGP,
.local = 0,
.init = nv4_init_stb4400,
.close = nv4_close,
.speed_changed = nv4_speed_changed,
.force_redraw = nv4_force_redraw,
.available = nv4_available,
.config = nv4_config,
};

View File

@@ -0,0 +1,92 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* Provides NV4 configuration
*
*
* Authors: Connor Hyde, <mario64crashed@gmail.com> I need a better email address ;^)
*
* Copyright 2024-2025 starfrost
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/io.h>
#include <86box/pci.h>
#include <86box/rom.h> // DEPENDENT!!!
#include <86box/video.h>
#include <86box/nv/vid_nv.h>
#include <86box/nv/vid_nv4.h>
const device_config_t nv4_config[] =
{
// Memory configuration
{
.name = "vram_size",
.description = "VRAM Size",
.type = CONFIG_SELECTION,
.default_int = NV4_VRAM_SIZE_16MB,
.selection =
{
// I thought this was never released, but it seems that at least one was released:
// The card was called the "NEC G7AGK"
{
.description = "8 MB",
.value = NV4_VRAM_SIZE_8MB,
},
{
.description = "16 MB",
.value = NV4_VRAM_SIZE_16MB,
},
}
},
// Multithreading configuration
{
.name = "pgraph_threads",
.description = "Render threads",
.type = CONFIG_SELECTION,
.default_int = 1, // todo: change later
.selection =
{
{
.description = "1 thread (Only use if issues appear with more threads)",
.value = 1,
},
{
.description = "2 threads",
.value = 2,
},
{
.description = "4 threads",
.value = 4,
},
{
.description = "8 threads",
.value = 8,
},
},
},
#ifndef RELEASE_BUILD
{
.name = "nv_debug_fulllog",
.description = "Disable Cyclical Lines Detection for nv_log (Use for getting full context at cost of VERY large log files)",
.type = CONFIG_BINARY,
.default_int = 0,
},
#endif
{
.type = CONFIG_END
}
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,46 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* NV4 debug register list
*
*
* Authors: Connor Hyde, <mario64crashed@gmail.com> I need a better email address ;^)
*
* Copyright 2024-2025 starfrost
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/io.h>
#include <86box/pci.h>
#include <86box/rom.h>
#include <86box/video.h>
#include <86box/nv/vid_nv.h>
#include <86box/nv/vid_nv4.h>
#ifdef NV_LOG
nv_register_t nv4_registers[] = {
{ NV4_PTIMER_INTR, "NV4 PTIMER - Interrupt Status", NULL, NULL},
{ NV4_PTIMER_INTR_EN, "NV4 PTIMER - Interrupt Enable", NULL, NULL,},
{ NV4_PTIMER_NUMERATOR, "NV4 PTIMER - Numerator", NULL, NULL, },
{ NV4_PTIMER_DENOMINATOR, "NV4 PTIMER - Denominator", NULL, NULL, },
{ NV4_PTIMER_TIME_0_NSEC, "NV4 PTIMER - Time0", NULL, NULL, },
{ NV4_PTIMER_TIME_1_NSEC, "NV4 PTIMER - Time1", NULL, NULL, },
{ NV4_PTIMER_ALARM_NSEC, "NV4 PTIMER - Alarm", NULL, NULL, },
{ NV4_PRAMDAC_VPLL_COEFF, "NV4 PRAMDAC - Pixel Clock Coefficient", NULL, NULL, },
{ NV4_PRAMDAC_NVPLL_COEFF, "NV4 PRAMDAC - GPU Core Clock Coefficient", NULL, NULL, },
{ NV4_PRAMDAC_MPLL_COEFF, "NV4 PRAMDAC - VRAM Clock Coefficient", NULL, NULL, },
{ NV_REG_LIST_END, NULL, NULL, NULL}, // sentinel value
};
#endif

View File

@@ -0,0 +1,84 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* NV4/Riva TNT - RAMDAC
*
*
* Authors: Connor Hyde, <mario64crashed@gmail.com> I need a better email address ;^)
*
* Copyright 2024-2025 starfrost
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/pci.h>
#include <86box/rom.h> // DEPENDENT!!!
#include <86box/video.h>
#include <86box/nv/vid_nv.h>
#include <86box/nv/vid_nv4.h>
uint64_t nv4_pramdac_get_hz(uint32_t coeff, bool apply_divider)
{
uint32_t m = coeff & 0xFF;
uint32_t n = (coeff >> 8) & 0xFF;
uint32_t p = (coeff >> 16) & 0x07;
// Check clock base
uint32_t hz_base = (nv4->straps & (1 << NV4_STRAP_CRYSTAL)) ? 14318180 : 13500000;
uint32_t final_hz = (hz_base * n) / (m << p);
// Check VCLK divider
if (apply_divider)
{
if (nv4->pramdac.clk_coeff_select & (1 << NV4_PRAMDAC_PLL_COEFF_SELECT_VCLK_RATIO))
final_hz >>= 1;
}
return final_hz;
}
void nv4_pramdac_set_vclk()
{
uint64_t final_hz = nv4_pramdac_get_hz(nv4->pramdac.nvclk, false);
//TODO: Everything
if (nv4->nvbase.nv4_vclk_timer)
timer_set_delay_u64(nv4->nvbase.nv4_vclk_timer, final_hz / TIMER_USEC);
}
uint32_t nv4_pramdac_read(uint32_t address)
{
uint32_t ret = 0x00;
switch (address)
{
case NV4_PRAMDAC_VPLL_COEFF: // Pixel clock
ret = nv4->pramdac.vclk;
break;
case NV4_PRAMDAC_NVPLL_COEFF: // System clock
ret = nv4->pramdac.nvclk;
break;
case NV4_PRAMDAC_MPLL_COEFF: // Memory clock
ret = nv4->pramdac.mclk;
break;
}
}
void nv4_pramdac_write(uint32_t address, uint32_t data)
{
}

Some files were not shown because too many files have changed in this diff Show More