2026-03-01 02:38:58 +02:00

265 lines
14 KiB
C

// gdraw_psp2.h - author: Fabian Giesen - copyright 2014 RAD Game Tools
//
// Interface for creating a PSP2 GDraw driver.
#include "gdraw.h"
#define IDOC
//idoc(parent,GDraw_psp2)
// Size and alignment requirements of GDraw context memory.
#define GDRAW_PSP2_CONTEXT_MEM_SIZE (16*1024)
// Alignment requirements for different resource types (in bytes)
#define GDRAW_PSP2_TEXTURE_ALIGNMENT 16
#define GDRAW_PSP2_VERTEXBUFFER_ALIGNMENT 16
typedef enum gdraw_psp2_resourcetype
{
GDRAW_PSP2_RESOURCE_texture,
GDRAW_PSP2_RESOURCE_vertexbuffer,
GDRAW_PSP2_RESOURCE__count,
} gdraw_psp2_resourcetype;
typedef struct
{
U32 allocs_attempted; // number of allocations attempted from the staging buffer
U32 allocs_succeeded; // number of allocations that succeeded
U32 bytes_attempted; // number of bytes attempted to allocate
U32 bytes_succeeded; // number of bytes successfully allocated
U32 largest_bytes_attempted; // number of bytes in largest attempted alloc
U32 largest_bytes_succeeded; // number of bytes in lagrest successful alloc
} gdraw_psp2_dynamic_stats;
typedef struct
{
void *start; // pointer to the start of the buffer
U32 size_in_bytes; // size of the buffer in bytes
U64 sync; // used internally by GDraw for synchronization.
gdraw_psp2_dynamic_stats stats; // stats on buffer usage - these are for your benefit!
} gdraw_psp2_dynamic_buffer;
IDOC extern void gdraw_psp2_InitDynamicBuffer(gdraw_psp2_dynamic_buffer *buffer, void *ptr, U32 num_bytes);
/* Initializes a GDraw dynamic buffer struct.
The "dynamic buffer" is where GDraw stores all transient data for a scene - dynamic
vertex and index data, uniform buffers and the like. We wrap them in a struct
so we can handle synchronization with the GPU.
"ptr" should point to non-cached, GPU-mapped readable memory. "num_bytes" is the size of
the buffer in bytes. */
IDOC extern void gdraw_psp2_WaitForDynamicBufferIdle(gdraw_psp2_dynamic_buffer *buffer);
/* Waits until a GDraw dynamic buffer is idle, i.e. not being used by the
GPU anymore. You need to call this if you intend to free the allocated storage. */
IDOC extern int gdraw_psp2_SetResourceMemory(gdraw_psp2_resourcetype type, S32 num_handles, void *ptr, S32 num_bytes);
/* Sets up the resource pools that GDraw uses for its video memory management.
It sets both the number of handles and the address and size of memory to use.
GDraw keeps track of allocations in each pool, and will free old resources in
a LRU manner to make space if one of the limits is about to be exceeded. It will
also automatically defragment memory if necessary to fulfill an allocation
request.
"ptr" points to the address of the resource pool. This memory needs to be
mapped to the GPU and *writeable*. If it isn't, the GPU will crash during
either this function or CreateContext!
Pass in NULL for "ptr" and zero "num_bytes" to free the memory allocated to
a specific pool.
GDraw can run into cases where resource memory gets fragmented; we defragment
automatically in that case. However, to make this work, GDraw on PSP2 needs
resource pool memory equivalent to *twice* the largest working set in any
scene. So for example, if you use 10MB worth of textures, GDraw needs at least
a 20MB texture pool! On other platforms, we can avoid this extra cost by
draining the GPU pipeline in the middle of a frame in certain rare cases, but
doing so on PSP2 would mean not supporting deferred contexts.
You need to set up all of the resource pools before you can start rendering.
If you modify this at runtime, you need to call IggyPlayerFlushAll on all
active Iggys (if any) since this call invalidates all resource handles they
currently hold.
Resource pool memory has certain alignment requirements - see the #defines
above. If you pass in an unaligned pointer, GDraw will automatically clip off
some bytes at the front to make the buffer aligned - in other words, you get
somewhat less usable bytes, but it should work fine.
If any Iggy draw calls are in flight, this call will block waiting for
those calls to finish (i.e. for the resource memory to become
unused).
*/
IDOC extern void gdraw_psp2_ResetAllResourceMemory();
/* Frees all resource pools managed by GDraw.
Use this as a quick way of freeing (nearly) all memory allocated by GDraw
without shutting it down completely. For example, you might want to use this
to quickly flush all memory allocated by GDraw when transitioning between the
main menu and the game proper. Like with SetResourceMemory, you need to call
IggyPlayerFlushAll on all currently active Iggy players if you do this - although
we recommend that you only use this function when there aren't any. */
IDOC extern GDrawFunctions * gdraw_psp2_CreateContext(SceGxmShaderPatcher *shader_patcher, void *context_mem, volatile U32 *notification, SceGxmOutputRegisterFormat reg_format);
/* Creates a GDraw context for rendering using GXM. You need to pass in a pointer to
the shader patcher to use, a pointer to "context memory" which holds a few persistent
resources shared by all Iggys (it needs to hold GDRAW_PSP2_CONTEXT_MEM_SIZE bytes
and must be mapped to be GPU readable), and a pointer to the notificaiton to use for
GDraw sync.
"reg_format" specifies the output register format to specify when creating
GDraw's fragment shaders. This should match your color surface. Typical choices
are:
- SCE_GXM_OUTPUT_REGISTER_FORMAT_UCHAR4 (32-bit output register size in color surface)
- SCE_GXM_OUTPUT_REGISTER_FORMAT_HALF4 (64-bit output register size in color surface)
There can only be one GDraw context active at any one time. The shader_patcher must
be valid for as long as a GDraw context is alive.
If initialization fails for some reason (the main reason would be an out of memory condition),
NULL is returned. Otherwise, you can pass the return value to IggySetGDraw. */
IDOC extern void gdraw_psp2_DestroyContext(void);
/* Destroys the current GDraw context, if any.
If any Iggy draw calls are in flight, this call will block waiting for
those calls to finish (i.e. for the resource memory to become
unused).
*/
IDOC extern void gdraw_psp2_Begin(SceGxmContext *context, const SceGxmColorSurface *color, const SceGxmDepthStencilSurface *depth,
gdraw_psp2_dynamic_buffer *dynamic_buffer);
/* This sets the SceGxmContext that GDraw writes its commands to. It also specifies
the dynamic buffer to use. Any GDraw / Iggy rendering calls outside a
Begin / End bracket are an error and will be treated as such. The GXM context
and dynamic buffer must be live during the entire Begin / End bracket.
NOTE: If you are passing in a deferred context, see important notes below!
GDraw uses the color and depth surfaces for parameter validation. Make sure to
pass in the same values you passed in to sceGxmBeginScene. Also, inside a given
scene, you may have only *one* $gdraw_psp2_Begin / $gdraw_psp2_End pair.
GDraw maintains a persistent resource cache shared across all Iggys. Because of this,
it is *vital* that all command lists generated from GDraw be executed in the order
they were generated, or the resource pools might get corrupted.
If the dynamic buffer is of insufficient size, GDraw will not be able to allocate dynamic
vertex data or upload new texture/vertex buffer data during some frames, resulting in
glitching! (When this happens, it will be reported as a warning, so make sure to install
a warning callback).
You should use multiple dynamic buffers (at least double-buffer it); otherwise,
GDraw needs to stall every frame to wait for the GPU to process the previous one.
GDraw starts no scenes of its own; you must call BeginScene before you issue
gdraw_psp2_Begin.
If you are a using a deferred context:
--------------------------------------
It's allowed to pass in a deferred context as "context". You may perform Iggy
rendering on a separate thread. However, because there is only a single global
resource pool that is modified directly by GDraw, rendering is *not* thread-safe;
that is, you may do Iggy rendering on any thread, but only a single thread may
be inside a GDraw Begin / End bracket at any given time.
Furthermore, if you use a deferred context, you must use (and execute!) the
resulting command list and end the containing scene *before* you call
$gdraw_psp2_Begin again. That is, usage must look like this:
Main thread: | Other thread:
<other work> | gdraw_psp2_Begin(deferred_ctx, ...)
| <render Iggys>
| gdraw_psp2_End(deferred_ctx, ...)
| ...
| sceGxmEndCommandList(deferred_ctx, &cmd_list)
|
sceGxmBeginScene(...) | <other work>
sceGxmExecuteCommandList(..., cmd_list) |
sceGxmEndScene() |
The second thread may *not* start another $gdraw_psp2_Begin before the main thread
calls sceGxmEndScene().
This is inconvenient, but unfortunately required by our resource management: every
GDraw Begin / End Bracket may end up having to wait for the GPU to finish work done
during a previous bracket, and this only works if the corresponding jobs have been
submitted to the GPU by the point $gdraw_psp2_Begin is called!
*/
IDOC extern SceGxmNotification gdraw_psp2_End();
/* This marks the end of GDraw rendering for a frame. It also triggers end-of-frame processing,
which is important for GDraw's internal resource management. GDraw will not touch the GXM context
or staging buffer after this call, so you are free to append other rendering commands after
this call returns.
This function will also update the "stats" field in the dynamic buffer you passed
to "Begin".
You are *required* to pass the returned SceGxmNotification as your "fragment notification"
when calling sceGxmEndScene. This is necessary to make GDraw's resource management work! */
IDOC extern void gdraw_psp2_SetTileOrigin(S32 x, S32 y);
/* This sets the x/y position of the output location of the top-left pixel of the current
tile. Iggy has support for manual splitting of rendering into multiple tiles; PSP2 is
already a tiled renderer that handles all this in hardware, so in practice
you will probably always pass (0,0) here.
You should call this inside a gdraw_psp2_Begin / gdraw_psp2_End bracket, before
any Iggy / GDraw rendering takes place. */
IDOC extern void gdraw_psp2_ClearBeforeNextRender(const F32 clear_color_rgba[4]);
/* You often want to clear the render target before you start Iggy rendering.
This is a convenience function, if you don't want to do the clear yourself.
Iggy always clears the depth/stencil buffers when it starts rendering; if you
call this function first, it will also clear the color surface to the
specified color during that initial clear. If this function is not called
before rendering, GDraw will leave the contents of the color surface alone.
This function does not do any rendering; it just sets some internal state
that GDraw processes when it starts rendering. That state gets reset for every
call to IggyPlayerDraw or IggyPlayerDrawTile. */
IDOC extern void RADLINK gdraw_psp2_CalculateCustomDraw_4J(IggyCustomDrawCallbackRegion *region, float matrix[16]);
IDOC extern void RADLINK gdraw_psp2_BeginCustomDraw(IggyCustomDrawCallbackRegion *region, float matrix[16]);
/* Call at the beginning of Iggy custom draw callback to clear any odd render states GDraw has
set, and to get the current 2D object-to-world transformation. */
IDOC extern void RADLINK gdraw_psp2_EndCustomDraw(IggyCustomDrawCallbackRegion *region);
/* Call at the end of Iggy custom draw callback so GDraw can restore its render states. */
IDOC extern GDrawTexture *gdraw_psp2_WrappedTextureCreate(SceGxmTexture *tex);
/* Create a wrapped texture from a GXM texture.
A wrapped texture can be used to let Iggy draw using the contents of a texture
you create and manage on your own. For example, you might render to this texture,
or stream video into it. Wrapped textures take up a handle. They will never be
freed or otherwise modified by GDraw; nor will GDraw change any reference counts.
All this is up to the application.
GDraw makes a copy of the contents of the sceGxmTexture (the contents of the struct
that is, not the data it points to). If you later modify the fields of "tex", you
need to call $gdraw_psp2_WrappedTextureChange.
*/
IDOC extern void gdraw_psp2_WrappedTextureChange(GDrawTexture *handle, SceGxmTexture *tex);
/* Switch an existing GDrawTexture * that represents a wrapped texture to use
a new underlying GXM texture. For example, you might internally double-buffer
a dynamically updated texture. As above, GDraw will leave this texture alone
and not touch any reference counts. */
IDOC extern void gdraw_psp2_WrappedTextureDestroy(GDrawTexture *handle);
/* Destroys the GDraw wrapper for a wrapped texture object. This will free up
a GDraw texture handle but not release the associated GXM texture; that is
up to you. */
IDOC extern GDrawTexture * RADLINK gdraw_psp2_MakeTextureFromResource(U8 *file_in_memory, S32 length, IggyFileTexturePSP2 *tex);
/* Sets up a texture loaded from a .psp2.iggytex file. */
extern void RADLINK gdraw_psp2_DestroyTextureFromResource(GDrawTexture *tex);