[4/6] wined3d: Store GLSL programs in a hash table rather than a linked list

H. Verbeet hverbeet at gmail.com
Tue Feb 27 13:51:53 CST 2007


Changelog:
  - Store GLSL programs in a hash table rather than a linked list
-------------- next part --------------
---

 dlls/wined3d/device.c          |   82 +--------------------------
 dlls/wined3d/directx.c         |   21 +++++++
 dlls/wined3d/glsl_shader.c     |  123 ++++++++++++++++++++++------------------
 dlls/wined3d/pixelshader.c     |   11 +++-
 dlls/wined3d/vertexshader.c    |   11 +++-
 dlls/wined3d/wined3d_private.h |   22 +++++--
 6 files changed, 128 insertions(+), 142 deletions(-)

diff --git a/dlls/wined3d/device.c b/dlls/wined3d/device.c
index e614559..f73ceb7 100644
--- a/dlls/wined3d/device.c
+++ b/dlls/wined3d/device.c
@@ -32,7 +32,6 @@
 #include "wined3d_private.h"
 
 WINE_DEFAULT_DEBUG_CHANNEL(d3d);
-WINE_DECLARE_DEBUG_CHANNEL(d3d_shader);
 #define GLINFO_LOCATION ((IWineD3DImpl *)(This->wineD3D))->gl_info
 
 /* Define the default light parameters as specified by MSDN */
@@ -99,6 +98,7 @@ static void set_depth_stencil_fbo(IWineD3DDevice *iface, IWineD3DSurface *depth_
     object->parent       = parent; \
     object->ref          = 1; \
     object->baseShader.device = (IWineD3DDevice*) This; \
+    list_init(&object->baseShader.linked_programs); \
     *pp##type = (IWineD3D##type *) object; \
 }
 
@@ -149,76 +149,6 @@ static void set_depth_stencil_fbo(IWineD3DDevice *iface, IWineD3DSurface *depth_
 const float identity[16] = {1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1};  /* When needed for comparisons */
 
 /**********************************************************
- * GLSL helper functions follow
- **********************************************************/
-
-/** Detach the GLSL pixel or vertex shader object from the shader program */
-static void detach_glsl_shader(IWineD3DDevice *iface, GLhandleARB shaderObj, GLhandleARB programId) {
-
-    IWineD3DDeviceImpl *This = (IWineD3DDeviceImpl *)iface;
-
-    if (shaderObj != 0 && programId != 0) {
-        TRACE_(d3d_shader)("Detaching GLSL shader object %u from program %u\n", shaderObj, programId);
-        GL_EXTCALL(glDetachObjectARB(programId, shaderObj));
-        checkGLcall("glDetachObjectARB");
-    }
-}
-
-/** Delete a GLSL shader program */
-static void delete_glsl_shader_program(IWineD3DDevice *iface, GLhandleARB obj) {
-
-    IWineD3DDeviceImpl *This = (IWineD3DDeviceImpl *)iface;
-    
-    if (obj != 0) {
-        TRACE_(d3d_shader)("Deleting GLSL shader program %u\n", obj);
-        GL_EXTCALL(glDeleteObjectARB(obj));
-        checkGLcall("glDeleteObjectARB");
-    }
-}
-
-/** Delete the list of linked programs this shader is associated with.
- * Also at this point, check to see if there are any objects left attached
- * to each GLSL program.  If not, delete the GLSL program object.
- * This will be run when a device is released. */
-static void delete_glsl_shader_list(IWineD3DDevice* iface) {
-    
-    struct list *ptr                       = NULL;
-    struct glsl_shader_prog_link *curLink  = NULL;
-    IWineD3DDeviceImpl *This               = (IWineD3DDeviceImpl *)iface;
-    
-    int numAttached = 0;
-    int i;
-    GLhandleARB objList[2];   /* There should never be more than 2 objects attached 
-                                 (one pixel shader and one vertex shader at most) */
-    
-    ptr = list_head( &This->glsl_shader_progs );
-    while (ptr) {
-        /* First, get the current item,
-         * save the link to the next pointer, 
-         * detach and delete shader objects,
-         * then de-allocate the list item's memory */
-        curLink = LIST_ENTRY( ptr, struct glsl_shader_prog_link, entry );
-        ptr = list_next( &This->glsl_shader_progs, ptr );
-
-        /* See if this object is still attached to the program - it may have been detached already */
-        GL_EXTCALL(glGetAttachedObjectsARB(curLink->programId, 2, &numAttached, objList));
-        TRACE_(d3d_shader)("%i GLSL objects are currently attached to program %u\n", numAttached, curLink->programId);
-        for (i = 0; i < numAttached; i++) {
-            detach_glsl_shader(iface, objList[i], curLink->programId);
-        }
-        
-        delete_glsl_shader_program(iface, curLink->programId);
-
-        /* Free the uniform locations */
-        HeapFree(GetProcessHeap(), 0, curLink->vuniformF_locations);
-        HeapFree(GetProcessHeap(), 0, curLink->puniformF_locations);
-
-        /* Free the memory for this list item */    
-        HeapFree(GetProcessHeap(), 0, curLink);
-    }
-}
-
-/**********************************************************
  * IUnknown parts follows
  **********************************************************/
 
@@ -261,15 +191,12 @@ static ULONG WINAPI IWineD3DDeviceImpl_Release(IWineD3DDevice *iface) {
 
         HeapFree(GetProcessHeap(), 0, This->draw_buffers);
 
+        if (This->glsl_program_lookup) hash_table_destroy(This->glsl_program_lookup);
+
         /* TODO: Clean up all the surfaces and textures! */
         /* NOTE: You must release the parent if the object was created via a callback
         ** ***************************/
 
-        /* Delete any GLSL shader programs that may exist */
-        if (This->vs_selected_mode == SHADER_GLSL ||
-            This->ps_selected_mode == SHADER_GLSL)
-            delete_glsl_shader_list(iface);
-
         /* Release the update stateblock */
         if(IWineD3DStateBlock_Release((IWineD3DStateBlock *)This->updateStateBlock) > 0){
             if(This->updateStateBlock != This->stateBlock)
@@ -1764,9 +1691,6 @@ static HRESULT WINAPI IWineD3DDeviceImpl_Init3D(IWineD3DDevice *iface, WINED3DPR
     IWineD3DImpl_CheckGraphicsMemory();
 #endif
 
-    /* Initialize our list of GLSL programs */
-    list_init(&This->glsl_shader_progs);
-
     { /* Set a default viewport */
         WINED3DVIEWPORT vp;
         vp.X      = 0;
diff --git a/dlls/wined3d/directx.c b/dlls/wined3d/directx.c
index 095b0e9..577e0db 100644
--- a/dlls/wined3d/directx.c
+++ b/dlls/wined3d/directx.c
@@ -2386,6 +2386,26 @@ static HRESULT WINAPI IWineD3DImpl_GetDeviceCaps(IWineD3D *iface, UINT Adapter,
     return WINED3D_OK;
 }
 
+static unsigned int glsl_program_key_hash(void *key) {
+    glsl_program_key_t *k = (glsl_program_key_t *)key;
+
+    unsigned int hash = k->vshader | k->pshader << 16;
+    hash += ~(hash << 15);
+    hash ^=  (hash >> 10);
+    hash +=  (hash << 3);
+    hash ^=  (hash >> 6);
+    hash += ~(hash << 11);
+    hash ^=  (hash >> 16);
+
+    return hash;
+}
+
+static BOOL glsl_program_key_compare(void *keya, void *keyb) {
+    glsl_program_key_t *ka = (glsl_program_key_t *)keya;
+    glsl_program_key_t *kb = (glsl_program_key_t *)keyb;
+
+    return ka->vshader == kb->vshader && ka->pshader == kb->pshader;
+}
 
 /* Note due to structure differences between dx8 and dx9 D3DPRESENT_PARAMETERS,
    and fields being inserted in the middle, a new structure is used in place    */
@@ -2456,6 +2476,7 @@ static HRESULT  WINAPI IWineD3DImpl_CreateDevice(IWineD3D *iface, UINT Adapter,
     select_shader_mode(&This->gl_info, DeviceType, &object->ps_selected_mode, &object->vs_selected_mode);
     if (object->ps_selected_mode == SHADER_GLSL || object->vs_selected_mode == SHADER_GLSL) {
         object->shader_backend = &glsl_shader_backend;
+        object->glsl_program_lookup = hash_table_create(&glsl_program_key_hash, &glsl_program_key_compare);
     } else if (object->ps_selected_mode == SHADER_ARB || object->vs_selected_mode == SHADER_ARB) {
         object->shader_backend = &arb_program_shader_backend;
     } else {
diff --git a/dlls/wined3d/glsl_shader.c b/dlls/wined3d/glsl_shader.c
index f270af7..ae2bf08 100644
--- a/dlls/wined3d/glsl_shader.c
+++ b/dlls/wined3d/glsl_shader.c
@@ -1994,16 +1994,42 @@ void vshader_glsl_output_unpack(
     }
 }
 
-/** Attach a GLSL pixel or vertex shader object to the shader program */
-static void attach_glsl_shader(IWineD3DDevice *iface, IWineD3DBaseShader* shader) {
+static void add_glsl_program_entry(IWineD3DDeviceImpl *device, struct glsl_shader_prog_link *entry) {
+    glsl_program_key_t *key;
+
+    key = HeapAlloc(GetProcessHeap(), 0, sizeof(glsl_program_key_t));
+    key->vshader = entry->vshader;
+    key->pshader = entry->pshader;
+
+    hash_table_put(device->glsl_program_lookup, key, entry);
+}
+
+static struct glsl_shader_prog_link *get_glsl_program_entry(IWineD3DDeviceImpl *device,
+        GLhandleARB vshader, GLhandleARB pshader) {
+    glsl_program_key_t key;
+
+    key.vshader = vshader;
+    key.pshader = pshader;
+
+    return (struct glsl_shader_prog_link *)hash_table_get(device->glsl_program_lookup, &key);
+}
+
+void delete_glsl_program_entry(IWineD3DDevice *iface, struct glsl_shader_prog_link *entry) {
     IWineD3DDeviceImpl *This = (IWineD3DDeviceImpl *)iface;
     WineD3D_GL_Info *gl_info = &((IWineD3DImpl *)(This->wineD3D))->gl_info;
-    GLhandleARB shaderObj = ((IWineD3DBaseShaderImpl*)shader)->baseShader.prgId;
-    if (This->stateBlock->glsl_program && shaderObj != 0) {
-        TRACE("Attaching GLSL shader object %u to program %u\n", shaderObj, This->stateBlock->glsl_program->programId);
-        GL_EXTCALL(glAttachObjectARB(This->stateBlock->glsl_program->programId, shaderObj));
-        checkGLcall("glAttachObjectARB");
-    }
+    glsl_program_key_t *key;
+
+    key = HeapAlloc(GetProcessHeap(), 0, sizeof(glsl_program_key_t));
+    key->vshader = entry->vshader;
+    key->pshader = entry->pshader;
+    hash_table_remove(This->glsl_program_lookup, key);
+
+    GL_EXTCALL(glDeleteObjectARB(entry->programId));
+    if (entry->vshader) list_remove(&entry->vshader_entry);
+    if (entry->pshader) list_remove(&entry->pshader_entry);
+    HeapFree(GetProcessHeap(), 0, entry->vuniformF_locations);
+    HeapFree(GetProcessHeap(), 0, entry->puniformF_locations);
+    HeapFree(GetProcessHeap(), 0, entry);
 }
 
 /** Sets the GLSL program ID for the given pixel and vertex shader combination.
@@ -2011,61 +2037,50 @@ static void attach_glsl_shader(IWineD3DDevice *iface, IWineD3DBaseShader* shader
  * inside of the DrawPrimitive() part of the render loop).
  *
  * If a program for the given combination does not exist, create one, and store
- * the program in the list.  If it creates a program, it will link the given
- * objects, too.
- *
- * We keep the shader programs around on a list because linking
- * shader objects together is an expensive operation.  It's much
- * faster to loop through a list of pre-compiled & linked programs
- * each time that the application sets a new pixel or vertex shader
- * than it is to re-link them together at that time.
- *
- * The list will be deleted in IWineD3DDevice::Release().
+ * the program in the hash table.  If it creates a program, it will link the
+ * given objects, too.
  */
 static void set_glsl_shader_program(IWineD3DDevice *iface) {
     IWineD3DDeviceImpl *This               = (IWineD3DDeviceImpl *)iface;
     WineD3D_GL_Info *gl_info               = &((IWineD3DImpl *)(This->wineD3D))->gl_info;
     IWineD3DPixelShader  *pshader          = This->stateBlock->pixelShader;
     IWineD3DVertexShader *vshader          = This->stateBlock->vertexShader;
-    struct glsl_shader_prog_link *curLink  = NULL;
-    struct glsl_shader_prog_link *newLink  = NULL;
-    struct list *ptr                       = NULL;
+    struct glsl_shader_prog_link *entry    = NULL;
     GLhandleARB programId                  = 0;
     int i;
     char glsl_name[8];
 
-    ptr = list_head( &This->glsl_shader_progs );
-    while (ptr) {
-        /* At least one program exists - see if it matches our ps/vs combination */
-        curLink = LIST_ENTRY( ptr, struct glsl_shader_prog_link, entry );
-        if (vshader == curLink->vertexShader && pshader == curLink->pixelShader) {
-            /* Existing Program found, use it */
-            TRACE("Found existing program (%u) for this vertex/pixel shader combination\n",
-                   curLink->programId);
-            This->stateBlock->glsl_program = curLink;
-            return;
-        }
-        /* This isn't the entry we need - try the next one */
-        ptr = list_next( &This->glsl_shader_progs, ptr );
+    GLhandleARB vshader_id = (vshader && This->vs_selected_mode == SHADER_GLSL) ? ((IWineD3DBaseShaderImpl*)vshader)->baseShader.prgId : 0;
+    GLhandleARB pshader_id = (pshader && This->ps_selected_mode == SHADER_GLSL) ? ((IWineD3DBaseShaderImpl*)pshader)->baseShader.prgId : 0;
+    entry = get_glsl_program_entry(This, vshader_id, pshader_id);
+    if (entry) {
+        This->stateBlock->glsl_program = entry;
+        return;
     }
 
     /* If we get to this point, then no matching program exists, so we create one */
     programId = GL_EXTCALL(glCreateProgramObjectARB());
     TRACE("Created new GLSL shader program %u\n", programId);
 
-    /* Allocate a new link for the list of programs */
-    newLink = HeapAlloc(GetProcessHeap(), 0, sizeof(struct glsl_shader_prog_link));
-    newLink->programId    = programId;
-    This->stateBlock->glsl_program = newLink;
+    /* Create the entry */
+    entry = HeapAlloc(GetProcessHeap(), 0, sizeof(struct glsl_shader_prog_link));
+    entry->programId = programId;
+    entry->vshader = vshader_id;
+    entry->pshader = pshader_id;
+    /* Add the hash table entry */
+    add_glsl_program_entry(This, entry);
+
+    /* Set the current program */
+    This->stateBlock->glsl_program = entry;
 
     /* Attach GLSL vshader */
-    if (NULL != vshader && This->vs_selected_mode == SHADER_GLSL) {
-        int i;
+    if (vshader_id) {
         int max_attribs = 16;   /* TODO: Will this always be the case? It is at the moment... */
         char tmp_name[10];
 
-        TRACE("Attaching vertex shader to GLSL program\n");
-        attach_glsl_shader(iface, (IWineD3DBaseShader*)vshader);
+        TRACE("Attaching GLSL shader object %u to program %u\n", vshader_id, programId);
+        GL_EXTCALL(glAttachObjectARB(programId, vshader_id));
+        checkGLcall("glAttachObjectARB");
 
         /* Bind vertex attributes to a corresponding index number to match
          * the same index numbers as ARB_vertex_programs (makes loading
@@ -2081,34 +2096,34 @@ static void set_glsl_shader_program(IWineD3DDevice *iface) {
              GL_EXTCALL(glBindAttribLocationARB(programId, i, tmp_name));
         }
         checkGLcall("glBindAttribLocationARB");
-        newLink->vertexShader = vshader;
+
+        list_add_head(&((IWineD3DBaseShaderImpl *)vshader)->baseShader.linked_programs, &entry->vshader_entry);
     }
 
     /* Attach GLSL pshader */
-    if (NULL != pshader && This->ps_selected_mode == SHADER_GLSL) {
-        TRACE("Attaching pixel shader to GLSL program\n");
-        attach_glsl_shader(iface, (IWineD3DBaseShader*)pshader);
-        newLink->pixelShader = pshader;
+    if (pshader_id) {
+        TRACE("Attaching GLSL shader object %u to program %u\n", pshader_id, programId);
+        GL_EXTCALL(glAttachObjectARB(programId, pshader_id));
+        checkGLcall("glAttachObjectARB");
+
+        list_add_head(&((IWineD3DBaseShaderImpl *)pshader)->baseShader.linked_programs, &entry->pshader_entry);
     }
 
     /* Link the program */
     TRACE("Linking GLSL shader program %u\n", programId);
     GL_EXTCALL(glLinkProgramARB(programId));
     print_glsl_info_log(&GLINFO_LOCATION, programId);
-    list_add_head( &This->glsl_shader_progs, &newLink->entry);
 
-    newLink->vuniformF_locations = HeapAlloc(GetProcessHeap(), 0, sizeof(GLhandleARB) * GL_LIMITS(vshader_constantsF));
+    entry->vuniformF_locations = HeapAlloc(GetProcessHeap(), 0, sizeof(GLhandleARB) * GL_LIMITS(vshader_constantsF));
     for (i = 0; i < GL_LIMITS(vshader_constantsF); ++i) {
         snprintf(glsl_name, sizeof(glsl_name), "VC[%i]", i);
-        newLink->vuniformF_locations[i] = GL_EXTCALL(glGetUniformLocationARB(programId, glsl_name));
+        entry->vuniformF_locations[i] = GL_EXTCALL(glGetUniformLocationARB(programId, glsl_name));
     }
-    newLink->puniformF_locations = HeapAlloc(GetProcessHeap(), 0, sizeof(GLhandleARB) * GL_LIMITS(pshader_constantsF));
+    entry->puniformF_locations = HeapAlloc(GetProcessHeap(), 0, sizeof(GLhandleARB) * GL_LIMITS(pshader_constantsF));
     for (i = 0; i < GL_LIMITS(pshader_constantsF); ++i) {
         snprintf(glsl_name, sizeof(glsl_name), "PC[%i]", i);
-        newLink->puniformF_locations[i] = GL_EXTCALL(glGetUniformLocationARB(programId, glsl_name));
+        entry->puniformF_locations[i] = GL_EXTCALL(glGetUniformLocationARB(programId, glsl_name));
     }
-
-    return;
 }
 
 static GLhandleARB create_glsl_blt_shader(WineD3D_GL_Info *gl_info) {
diff --git a/dlls/wined3d/pixelshader.c b/dlls/wined3d/pixelshader.c
index e83cab1..e362f65 100644
--- a/dlls/wined3d/pixelshader.c
+++ b/dlls/wined3d/pixelshader.c
@@ -74,7 +74,16 @@ static ULONG  WINAPI IWineD3DPixelShaderImpl_Release(IWineD3DPixelShader *iface)
     ref = InterlockedDecrement(&This->ref);
     if (ref == 0) {
         if (This->baseShader.shader_mode == SHADER_GLSL && This->baseShader.prgId != 0) {
-            /* If this shader is still attached to a program, GL will perform a lazy delete */
+            struct list *linked_programs = &This->baseShader.linked_programs;
+
+            TRACE("Deleting linked programs\n");
+            if (linked_programs->next) {
+                struct glsl_shader_prog_link *entry, *entry2;
+                LIST_FOR_EACH_ENTRY_SAFE(entry, entry2, linked_programs, struct glsl_shader_prog_link, pshader_entry) {
+                    delete_glsl_program_entry(This->baseShader.device, entry);
+                }
+            }
+
             TRACE("Deleting shader object %u\n", This->baseShader.prgId);
             GL_EXTCALL(glDeleteObjectARB(This->baseShader.prgId));
             checkGLcall("glDeleteObjectARB");
diff --git a/dlls/wined3d/vertexshader.c b/dlls/wined3d/vertexshader.c
index f027032..5e79451 100644
--- a/dlls/wined3d/vertexshader.c
+++ b/dlls/wined3d/vertexshader.c
@@ -1113,7 +1113,16 @@ static ULONG WINAPI IWineD3DVertexShaderImpl_Release(IWineD3DVertexShader *iface
     ref = InterlockedDecrement(&This->ref);
     if (ref == 0) {
         if (This->baseShader.shader_mode == SHADER_GLSL && This->baseShader.prgId != 0) {
-            /* If this shader is still attached to a program, GL will perform a lazy delete */
+            struct list *linked_programs = &This->baseShader.linked_programs;
+
+            TRACE("Deleting linked programs\n");
+            if (linked_programs->next) {
+                struct glsl_shader_prog_link *entry, *entry2;
+                LIST_FOR_EACH_ENTRY_SAFE(entry, entry2, linked_programs, struct glsl_shader_prog_link, vshader_entry) {
+                    delete_glsl_program_entry(This->baseShader.device, entry);
+                }
+            }
+
             TRACE("Deleting shader object %u\n", This->baseShader.prgId);
             GL_EXTCALL(glDeleteObjectARB(This->baseShader.prgId));
             checkGLcall("glDeleteObjectARB");
diff --git a/dlls/wined3d/wined3d_private.h b/dlls/wined3d/wined3d_private.h
index c228574..add82a8 100644
--- a/dlls/wined3d/wined3d_private.h
+++ b/dlls/wined3d/wined3d_private.h
@@ -628,6 +628,7 @@ struct IWineD3DDeviceImpl
     int vs_selected_mode;
     int ps_selected_mode;
     const shader_backend_t *shader_backend;
+    hash_table_t *glsl_program_lookup;
 
     /* To store */
     BOOL                    view_ident;        /* true iff view matrix is identity                */
@@ -701,10 +702,6 @@ struct IWineD3DDeviceImpl
     WINED3DFORMAT ddraw_format;
     BOOL ddraw_fullscreen;
 
-    /* List of GLSL shader programs and their associated vertex & pixel shaders */
-    struct list glsl_shader_progs;
-
-
     /* Final position fixup constant */
     float                       posFixup[4];
 
@@ -1463,14 +1460,20 @@ typedef void (*SHADER_HANDLER) (struct SHADER_OPCODE_ARG*);
  * vertex shaders.  A list of this type is maintained on the DeviceImpl, and is only
  * used if the user is using GLSL shaders. */
 struct glsl_shader_prog_link {
-    struct list             entry;
+    struct list             vshader_entry;
+    struct list             pshader_entry;
     GLhandleARB             programId;
     GLhandleARB             *vuniformF_locations;
     GLhandleARB             *puniformF_locations;
-    IWineD3DVertexShader*   vertexShader;
-    IWineD3DPixelShader*    pixelShader;
+    GLhandleARB             vshader;
+    GLhandleARB             pshader;
 };
 
+typedef struct {
+    GLhandleARB vshader;
+    GLhandleARB pshader;
+} glsl_program_key_t;
+
 /* TODO: Make this dynamic, based on shader limits ? */
 #define MAX_REG_ADDR 1
 #define MAX_REG_TEMP 32
@@ -1600,6 +1603,8 @@ extern const SHADER_OPCODE* shader_get_opcode(
 extern void shader_delete_constant_list(
     struct list* clist);
 
+void delete_glsl_program_entry(IWineD3DDevice *iface, struct glsl_shader_prog_link *entry);
+
 /* Vertex shader utility functions */
 extern BOOL vshader_get_input(
     IWineD3DVertexShader* iface,
@@ -1727,6 +1732,9 @@ typedef struct IWineD3DBaseShaderClass
     /* Type of shader backend */
     int shader_mode;
 
+    /* Programs this shader is linked with */
+    struct list linked_programs;
+
     /* Immediate constants (override global ones) */
     struct list constantsB;
     struct list constantsF;


More information about the wine-patches mailing list