first pass at the device-independent drawing system, including the Python

representation of a device context
This commit is contained in:
Amy Bowersox 2019-12-10 12:42:58 -07:00
parent ee292d3aab
commit c3b3e0a350
10 changed files with 641 additions and 1 deletions

View File

@ -2,7 +2,7 @@ BUILDUTILS=../buildutils
RESOURCES=../resources
SPLASHSCREEN=splash-vmwcblk.png
OBJS=main.o sysinput.o ep_init.o ep_upiwin.o ep_backlight.o ep_msg.o ep_upiwin_tmp.o ep_util.o \
OBJS=main.o sysinput.o ep_init.o ep_upiwin.o ep_backlight.o ep_msg.o ep_devctxt.o ep_upiwin_tmp.o ep_util.o \
fbinit.o rect.o gfxobj.o devctxt.o dc_screen.o fontengine.o fbprimitive.o \
log.o gpio.o msg_queue.o time_func.o config.o splash.o
LIBS=-lpython3.7m -lcrypt -lfreetype -lbcm2835 -lpthread -ldl -lutil -lm

View File

@ -1,5 +1,175 @@
#include "fbinit.h"
#include "devctxt.h"
#include "dc_screen.h"
inline static PUINT16 loc_from_coords(PSCREENPRIVDATA priv, INT32 x, INT32 y)
{
return priv->pdata + (y * priv->pix_per_row) + x;
}
inline static UINT16 native_from_COLORREF(COLORREF cr)
{
return (UINT16)(((cr << 8) & 0xF800) | ((cr >> 5) & 0x7E0) | ((cr >> 19) & 0x1F));
}
inline static COLORREF COLORREF_from_native(UINT16 cr)
{
UINT32 tmp = cr;
return (COLORREF)(((tmp << 19) & 0xF80000) | ((tmp << 5) & 0xFC00) | ((tmp >> 8) & 0xF800));
}
static inline UINT16 apply_rop2(INT32 op, UINT16 disp, UINT16 pen)
{
switch (op)
{
case R2_BLACK:
return 0;
case R2_NOTMERGEPEN:
return ~(disp | pen);
case R2_MASKNOTPEN:
return disp & (~pen);
case R2_NOTCOPYPEN:
return ~pen;
case R2_MASKPENNOT;
return (~disp) & pen;
case R2_NOT:
return ~disp;
case R2_XORPEN:
return disp ^ pen;
case R2_NOTMASKPEN:
return ~(disp & pen);
case R2_MASKPEN:
return disp & pen;
case R2_NOTXORPEN:
return ~(disp ^ pen);
case R2_NOP:
return disp;
case R2_MERGENOTPEN:
return disp | (~pen);
case R2_COPYPEN:
return pen;
case R2_MERGEPENNOT:
return (~disp) | pen;
case R2_MERGEPEN:
return disp | pen;
case R2_WHITE:
return (UINT16)(-1);
}
}
static COLORREF screen_set_pixel(PVOID privdata, INT32 x, INT32 y, COLORREF color, INT32 op)
{
PSCREENPRIVDATA priv = (PSCREENPRIVDATA)privdata;
UINT16 pencolor = native_from_COLORREF(color);
PUINT16 loc = loc_from_coords(priv, x, y);
UINT16 screen = *loc;
*loc = apply_rop2(op, screen, pencolor);
return COLORREF_from_native(screen);
}
static BOOL screen_line(PVOID privdata, INT32 x1, INT32 y1, INT32 x2, INT32 y2, COLORREF color, INT32 op)
{
PSCREENPRIVDATA priv = (PSCREENPRIVDATA)privdata;
UINT16 pencolor = native_from_COLORREF(color);
INT32 dx = x2 - x1;
INT32 dy = y2 - y1;
INT32 tmp;
PUINT16 loc;
if (ABS(dx) < ABS(dy))
{
if (y1 > y2)
{
tmp = x1;
x1 = x2;
x2 = tmp;
tmp = y1;
y1 = y2;
y2 = tmp;
dx = -dx;
dy = -dy;
}
loc = loc_from_coords(priv, x1, y1);
tmp = x1;
x1 << 16;
dx = (dx << 16) / dy;
while (y1 <= y2)
{
*loc = apply_rop2(op, *loc, pencolor);
x1 += dx;
++y1;
loc += priv->pix_per_row;
if (tmp != (x1 >> 16))
{
loc += ((x1 >> 16) - tmp);
tmp = x1 >> 16;
}
}
}
else
{
if (x1 > x2)
{
tmp = x1;
x1 = x2;
x2 = tmp;
tmp = y1;
y1 = y2;
y2 = tmp;
dx = -dx;
dy = -dy;
}
loc = loc_from_coords(priv, x1, y1);
tmp = y1;
y1 <<= 16;
dy = dx ? (dy << 16) / dx : 0;
while (x1 <= x2)
{
*loc = apply_rop2(op, *loc, pencolor);
y1 += dy;
++x1;
++loc;
if (tmp != (y1 >> 16))
{
loc += ((((y1 >> 16) - tmp) * priv->pix_per_row);
tmp = y1 >> 16;
}
}
}
}
static const DCFUNTABLE screen_funtable = {
screen_set_pixel,
screen_line
};
static void screen_context_destroy(PVOID obj)
{
PDCTXT pdctxt = (PDCTXT)obj;
_DC_FinalizeCommon(pdctxt);
free(pdctxt->privdata);
}
PDCTXT DC_CreateScreenContext(void)
{
PDCTXT rc;
PSCREENPRIVDATA priv;
priv = (PSCREENPRIVDATA)malloc(sizeof(SCREENPRIVDATA));
if (!priv)
return NULL;
priv->pix_per_row = Fb_Info->width;
priv->pdata = Pb_Ptr;
rc = _DC_Allocate(&screen_funtable, priv);
if (rc)
{
rc->hdr.dtor = screen_context_destroy;
rc->cliprect.left = rc->cliprect.top = 0;
rc->cliprect.right = Fb_Info->width - 1;
rc->cliprect.bottom = Fb_Info->height - 1;
}
else
free(priv);
return rc;
}

View File

@ -9,4 +9,6 @@ typedef struct tagSCREENPRIVDATA {
UINT16 *pdata;
} SCREENPRIVDATA, *PSCREENPRIVDATA;
extern PDCTXT DC_CreateScreenContext(void);
#endif /* __DC_SCREEN_H_INCLUDED */

View File

@ -1,9 +1,171 @@
#include <string.h>
#include "gfxtype.h"
#include "gfxobj.h"
#include "devctxt.h"
inline static BYTE line_clip_outcode(INT32 x, INT32 y, INT32 xmin, INT32 ymin, INT32 xmax, INT32 ymax)
{
BYTE rc = 0;
if (y < ymin)
rc |= 0x8;
else if (y >= ymax)
rc |= 0x4;
if (x < xmin)
rc |= 0x1;
else if (x >= xmax)
rc |= 0x2;
return rc;
}
static BOOL line_clip(PINT32 output, INT32 x1, INT32 y1, INT32 x2, INT32 y2, INT32 xmin, INT32 ymin, INT32 xmax, INT32 ymax)
{
BYTE outcode1, outcode2, tmpb;
INT32 tmp;
/* Cohen-Sutherland line-clipping algorithm (see Foley & Van Dam, pp. 145-149) */
for (;;)
{
outcode1 = line_clip_outcode(x1, y1, xmin, ymin, xmax, ymax);
outcode2 = line_clip_outcode(x2, y2, xmin, ymin, xmax, ymax);
if ((outcode1 & outcode2) != 0)
return FALSE; /* trivial rejection */
else if ((outcode1 == 0) && (outcode2 == 0))
break; /* trivial acceptance */
if (outcode1 == 0)
{
tmp = x1;
x1 = x2;
x2 = tmp;
tmp = y1;
y1 = y2;
y2 = tmp;
tmpb = outcode1;
outcode1 = outcode2;
outcode2 = tmp;
}
if (outcode1 & 0x8)
{
x1 += (x2 - x1) * (ymin - y1) / (y2 - y1);
y1 = ymin;
}
else if (outcode1 & 0x4)
{
x1 += (x2 - x1) * (ymax - y1) / (y2 - y1);
y1 = ymax;
}
else if (outcode1 & 0x2)
{
y1 += (y2 - y1) * (xmax - x1) / (x2 - x1);
x1 = xmax;
}
else if (outcode1 & 0x1)
{
y1 += (y2 - y1) * (xmin - x1) / (x2 - x1);
x1 = xmin;
}
}
output[0] = x1;
output[1] = y1;
output[2] = x2;
output[3] = y2;
return TRUE;
}
static BOOL internal_line(PDCTXT pdctxt, INT32 x1, INT32 y1, INT32 x2, INT32 y2)
{
INT32 buffer[4];
if (line_clip(buffer, x1 << 16, y1 << 16, x2 << 16, y2 << 16, pdctxt->cliprect.left << 16, pdctxt->cliprect.top << 16,
pdctxt->cliprect.right << 16, pdctxt->cliprect.bottom << 16))
return (*pdctxt->funcs->line)(pdctxt->privdata, buffer[0] >> 16, buffer[1] >> 16, buffer[2] >> 16, buffer[3] >> 16, pdctxt->color, pdctxt->rop2);
return TRUE;
}
PDCTXT _DC_Allocate(PDCFUNTABLE funcs, PVOID privdata)
{
PDCTXT rc = (PDCTXT)malloc(sizeof(DCTXT));
if (!rc)
return NULL;
memset(rc, 0, sizeof(DCTXT));
_Go_init(&(rc->hdr), DCTXT_SIG_WORD, sizeof(DCTXT));
rc->funcs = funcs;
rc->privdata = privdata;
rc->rop2 = R2_COPYPEN;
return rc;
}
void _DC_FinalizeCommon(PDCTXT pdctxt)
{
/* nothing here yet */
}
COLORREF DC_SetPixel(PDCTXT pdctxt, INT32 x, INT32 y, COLORREF color)
{
if (!G_coords_in_rect(&(pdctxt->cliprect), x, y))
return (COLORREF)(-1);
return (*(pdctxt->funcs->set_pixel))(pdctxt->privdata, xm, y, colorref, pdctxt->rop2);
}
BOOL DC_LineTo(PDCTXT pdctxt, INT32 x, INT32 y)
{
BOOL rc = internal_line(pdctxt, pdctxt->pos.x, pdctxt->pos.y, x, y);
if (rc)
{
pdctxt->pos.x = x;
pdctxt->pos.y = y;
}
return rc;
}
BOOL DC_MoveTo(PDCTXT pdctxt, INT32 x, INT32 y, PPOINT oldpt)
{
if (oldpt)
memcpy(oldpt, &(pdctxt->pos), sizeof(POINT));
pdctxt->pos.x = x;
pdctxt->pos.y = y;
return TRUE;
}
BOOL DC_Rectangle(PDCTXT pdctxt, INT32 left, INT32 top, INT32 right, INT32 bottom)
{
internal_line(pdctxt, left, top, right - 1, top);
internal_line(pdctxt, left, top + 1, left, bottom - 2);
internal_line(pdctxt, right - 1, top + 1, right - 1, bottom - 2);
internal_line(pdctxt, left, bottom - 1, right - 1, bottom - 1);
return TRUE;
}
UINT32 DC_GetROP2(PDCTXT pdctxt)
{
return pdctxt->rop2;
}
UINT32 DC_SetROP2(PDCTXT pdctxt, UINT32 rop)
{
UINT32 rc = pdctxt->rop2;
pdctxt->rop2 = rop;
return rc;
}
COLORREF DC_GetTextColor(PDCTXT pdctxt)
{
return pdctxt->color;
}
COLORREF DC_SetTextColor(PDCTXT pdctxt, COLORREF cr)
{
COLORREF rc = pdctxt->color;
pdctxt->color = cr;
return rc;
}
BOOL DC_GetClipRect(PDCTXT pdctxt, PRECT prect)
{
memcpy(prect, &(pdctxt->cliprect), sizeof(RECT));
return TRUE;
}
BOOL DC_SetClipRect(PDCTXT pdctxt, PRECT prect)
{
memcpy(&(pdctxt->cliprect), prect, sizeof(RECT));
return TRUE;
}

View File

@ -26,9 +26,11 @@
#define R2_WHITE 16
typedef COLORREF (*DCtx_SetPixel)(PVOID privdata, INT32 x, INT32 y, COLORREF color, INT32 op);
typedef BOOL (*DCtx_Line)(PVOID privdata, INT32 x1, INT32 y1, INT32 x2, INT32 y2, COLORREF color, INT32 op);
typedef struct tagDCFUNTABLE {
DCtx_SetPixel set_pixel; /* sets a single pixel on the display */
DCtx_Line line; /* draws a line on the display */
} DCFUNTABLE;
typedef const DCFUNTABLE *PDCFUNTABLE;
@ -43,6 +45,21 @@ typedef struct tagDCTXT {
COLORREF color; /* current drawing color (XXX replace with pens later) */
} DCTXT, *PDCTXT;
extern PDCTXT _DC_Allocate(PDCFUNTABLE funcs, PVOID privdata);
extern void _DC_FinalizeCommon(PDCTXT pdctxt);
extern COLORREF DC_SetPixel(PDCTXT pdctxt, INT32 x, INT32 y, COLORREF color);
extern BOOL DC_LineTo(PDCTXT pdctxt, INT32 x, INT32 y);
extern BOOL DC_MoveTo(PDCTXT pdctxt, INT32 x, INT32 y, PPOINT oldpt);
extern BOOL DC_Rectangle(PDCTXT pdctxt, INT32 left, INT32 top, INT32 right, INT32 bottom);
extern UINT32 DC_GetROP2(PDCTXT pdctxt);
extern UINT32 DC_SetROP2(PDCTXT pdctxt, UINT32 rop);
extern COLORREF DC_GetTextColor(PDCTXT pdctxt);
extern COLORREF DC_SetTextColor(PDCTXT pdctxt, COLORREF cr);
extern BOOL DC_GetClipRect(PDCTXT pdctxt, PRECT prect);
extern BOOL DC_SetClipRect(PDCTXT pdctxt, PRECT prect);
#define DC_addref(pdctxt) Go_addref(&(pdctxt->hdr))
#define DC_release(pdctxt) Go_release(&(pdctxt->hdr))
#endif /* __DEVCTXT_H_INCLUDED */

253
src/ep_devctxt.c Executable file
View File

@ -0,0 +1,253 @@
#include <string.h>
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include "wintype.h"
#include "scode.h"
#include "devctxt.h"
#include "dc_screen.h"
#include "ep_init.h"
typedef struct tagDevCtxtObject {
PyObject_HEAD
PDCTXT pdctxt;
} DevCtxtObject;
static PyObject *devctxt_set_pixel(DevCtxtObject *self, PyObject *args)
{
INT32 x, y;
COLORREF color, rc;
if (!PyArg_ParseTuple(args, "iik", &x, &y, &color))
return NULL;
if (!(self->pdctxt))
{
PyErr_SetString(PyExc_RuntimeError, "bad device context");
return NULL;
}
rc = DC_SetPixel(self->pdctxt, x, y, color);
return PyLong_FromUnsignedLong(rc);
}
static PyObject *devctxt_line_to(DevCtxtObject *self, PyObject *args)
{
INT32 x, y;
BOOL rc;
if (!PyArg_ParseTuple(args, "ii", &x, &y))
return NULL;
if (!(self->pdctxt))
{
PyErr_SetString(PyExc_RuntimeError, "bad device context");
return NULL;
}
rc = DC_LineTo(self->pdctxt, x, y);
return PyBool_FromLong(rc);
}
static PyObject *devctxt_move_to(DevCtxtObject *self, PyObject *args)
{
INT32 x, y;
BOOL rc;
if (!PyArg_ParseTuple(args, "ii", &x, &y))
return NULL;
if (!(self->pdctxt))
{
PyErr_SetString(PyExc_RuntimeError, "bad device context");
return NULL;
}
rc = DC_MoveTo(self->pdctxt, x, y, NULL);
return PyBool_FromLong(rc);
}
static PyObject *devctxt_rectangle(DevCtxtObject *self, PyObject *args)
{
INT32 left, top, right, bottom;
BOOL rc;
if (!PyArg_ParseTuple(args, "iiii", &left, &top, &right, &bottom))
return NULL;
if (!(self->pdctxt))
{
PyErr_SetString(PyExc_RuntimeError, "bad device context");
return NULL;
}
rc = DC_Rectangle(self->pdctxt, left, top, right, bottom);
return PyBool_FromLong(rc);
}
static PyObject *devctxt_get_clip_rect(DevCtxtObject *self, PyObject *args)
{
RECT rect;
if (!PyArg_ParseTuple(args, ""))
return NULL;
if (!(self->pdctxt))
{
PyErr_SetString(PyExc_RuntimeError, "bad device context");
return NULL;
}
DC_GetClipRect(self->pdctxt, &rect);
return Py_BuildValue("(i,i,i,i)", rect.left, rect.top, rect.right, rect.bottom);
}
static PyObject *devctxt_set_clip_rect(DevCtxtObject *self, PyObject *args)
{
RECT rect;
BOOL rc;
if (!PyArg_ParseTuple(args, "iiii", &(rect.left), &(rect.top), &(rect.right), &(rect.bottom)))
return NULL;
if (!(self->pdctxt))
{
PyErr_SetString(PyExc_RuntimeError, "bad device context");
return NULL;
}
rc = DC_SetClipRect(self->pdctxt, &rect);
return PyBool_FromLong(rc);
}
static PyMethodDef DevCtxtMethods[] = {
{"set_pixel", (PyCFunction)devctxt_set_pixel, METH_VARARGS,
"Sets a single pixel on the display."},
{"line_to", (PyCFunction)devctxt_line_to, METH_VARARGS,
"Draws a line from the current position to the specified location."},
{"move_to", (PyCFunction)devctxt_move_to, METH_VARARGS,
"Draws a line from the current position to the specified location."},
{"rectangle", (PyCFunction)devctxt_rectangle, METH_VARARGS,
"Draws a rectangle."},
{"get_clip_rect", (PyCFunction)devctxt_get_clip_rect, METH_VARARGS,
"Returns the current clipping rectangle of the device context."},
{"set_clip_rect", (PyCFunction)devctxt_set_clip_rect, METH_VARARGS,
"Sets the current clipping rectangle of the device context."},
{NULL, NULL, 0, NULL}
};
static PyObject *devctxt_get_rop2(DevCtxtObject *self, void *closure)
{
if (!(self->pdctxt))
{
PyErr_SetString(PyExc_RuntimeError, "bad device context");
return NULL;
}
return PyLong_FromUnsignedLong(DC_GetROP2(self->pdctxt));
}
static int devctxt_set_rop2(DevCtxtObject *self, PyObject *value, void *closure)
{
UINT32 v;
if (!(self->pdctxt))
{
PyErr_SetString(PyExc_RuntimeError, "bad device context");
return -1;
}
if (value == NULL)
{
PyErr_SetString(PyExc_TypeError, "Cannot delete this attribute");
return -1;
}
v = PyLong_AsUnsignedLong(value);
if (PyErr_Occurred())
return -1;
DC_SetROP2(self->pdctxt, v);
return 0;
}
static PyObject *devctxt_get_text_color(DevCtxtObject *self, void *closure)
{
if (!(self->pdctxt))
{
PyErr_SetString(PyExc_RuntimeError, "bad device context");
return NULL;
}
return PyLong_FromUnsignedLong(DC_GetTextColor(self->pdctxt));
}
static int devctxt_set_text_color(DevCtxtObject *self, PyObject *value, void *closure)
{
COLORREF v;
if (!(self->pdctxt))
{
PyErr_SetString(PyExc_RuntimeError, "bad device context");
return -1;
}
if (value == NULL)
{
PyErr_SetString(PyExc_TypeError, "Cannot delete this attribute");
return -1;
}
v = PyLong_AsUnsignedLong(value);
if (PyErr_Occurred())
return -1;
DC_SetTextColor(self->pdctxt, v);
return 0;
}
static PyGetSetDef DevCtxtProperties[] = {
{"rop2", (getter)devctxt_get_rop2, (setter)devctxt_set_rop2,
"Current raster operation", NULL},
{"text_color", (getter)devctxt_get_text_color, (setter)devctxt_set_text_color,
"Current text color", NULL},
{NULL, NULL, NULL, NULL, NULL}
};
static void devctxt_dealloc(DevCtxtObject *self)
{
if (self->pdctxt)
DC_release(self->pdctxt);
Py_TYPE(self)->tp_free((PyObject *)self);
}
static int devctxt_init(DevCtxtObject *self, PyObject *args, PyObject *kwds)
{
static char *kwlist[] = { "type" }
char *type;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|s", kwlist, &type))
return -1;
if (stricmp(type, "screen") == 0)
{
self->pdctxt = DC_CreateScreenContext();
if (!(self->pdctxt))
{
PyErr_SetString(PyExc_RuntimeError, "unable to create screen context");
return -1
}
}
else
{
PyErr_Format(PyExc_RuntimeError, "invalid type '%s'", type);
return -1;
}
return 0;
}
static PyTypeObject DevCtxtType = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "upiwin.DevCtxt",
.tp_doc = "Device context object",
.tp_basicsize = sizeof(DevCtxtObject),
.tp_itemsize = 0,
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_new = PyType_GenericNew,
.tp_init = (initproc)devctxt_init,
.tp_dealloc = (destructor)devctxt_dealloc,
.tp_methods = DevCtxtMethods,
.tp_getset = DevCtxtProperties,
}
HRESULT Epython_register_devctxt(PyObject *module)
{
if (PyType_Ready(&DevCtxtType) < 0)
return E_FAIL;
Py_INCREF(&DevCtxtType);
if (PyModule_AddObject(module, "DevCtxt", (PyObject *)(&DevCtxtType)) < 0)
{
Py_DECREF(&DevCtxtType);
return E_FAIL;
}
return S_OK;
}

View File

@ -14,6 +14,8 @@ extern PyObject *UPIWIN_tmp_module;
extern PyObject *Epython_init_upiwin_module(void);
extern PyObject *Epython_init_upiwin_tmp_module(void);
extern HRESULT Epython_register_devctxt(PyObject *module);
extern HRESULT Epython_setup(void);
extern HRESULT Epython_run(void);

View File

@ -6,6 +6,7 @@
#include "ep_init.h"
#include "ep_upiwin.h"
#include "ep_util.h"
#include "devctxt.h"
static PyMethodDef UPIWINMethods[] = {
/* Backlight control functions */
@ -46,6 +47,23 @@ BEGIN_CONSTANT_TABLE(UPIWINConstants)
CONSTANT_INT_MACRO(WM_TOUCHDOWN)
CONSTANT_INT_MACRO(WM_TOUCHMOVE)
CONSTANT_INT_MACRO(WM_TOUCHUP)
/* Raster op constants */
CONSTANT_INT_MACRO(R2_BLACK)
CONSTANT_INT_MACRO(R2_NOTMERGEPEN)
CONSTANT_INT_MACRO(R2_MASKNOTPEN)
CONSTANT_INT_MACRO(R2_NOTCOPYPEN)
CONSTANT_INT_MACRO(R2_MASKPENNOT)
CONSTANT_INT_MACRO(R2_NOT)
CONSTANT_INT_MACRO(R2_XORPEN)
CONSTANT_INT_MACRO(R2_NOTMASKPEN)
CONSTANT_INT_MACRO(R2_MASKPEN)
CONSTANT_INT_MACRO(R2_NOTXORPEN)
CONSTANT_INT_MACRO(R2_NOP)
CONSTANT_INT_MACRO(R2_MERGENOTPEN)
CONSTANT_INT_MACRO(R2_COPYPEN)
CONSTANT_INT_MACRO(R2_MERGEPENNOT)
CONSTANT_INT_MACRO(R2_MERGEPEN)
CONSTANT_INT_MACRO(R2_WHITE)
END_CONSTANT_TABLE
PyObject *Epython_init_upiwin_module(void)
@ -62,6 +80,12 @@ PyObject *Epython_init_upiwin_module(void)
Py_DECREF(module);
return NULL;
}
if (FAILED(Epython_register_devctxt(module)))
{
Py_DECREF(module);
return NULL;
}
/* set up the module state */
pstate = (PUPIWIN_STATE)PyModule_GetState(module);

View File

@ -1,6 +1,15 @@
#include <stdlib.h>
#include <string.h>
#include "gfxobj.h"
void _Go_init(PGFXOBJECT obj, UINT32 sig, UINT32 size)
{
memset(obj, 0, sizeof(GFXOBJECT));
obj->sig = sig;
obj->size = size;
obj->refcnt = 1;
}
void Go_unchain(PGFXOBJECT obj)
{
if (!(obj->next || obj->prev))

View File

@ -16,6 +16,7 @@ typedef struct tagGFXOBJECT {
struct tagGFXOBJECT *prev;
} GFXOBJECT, *PGFXOBJECT;
extern void _Go_init(PGFXOBJECT obj, UINT32 sig, UINT32 size);
extern void Go_unchain(PGFXOBJECT obj);
extern INT32 Go_addref(PGFXOBJECT obj);
extern INT32 Go_release(PGFXOBJECT obj);