Jump to content

  • Log In with Google      Sign In   
  • Create Account

AgentC

Member Since 16 May 2005
Offline Last Active Today, 03:15 AM

Topics I've Started

OpenGL framebuffer management

03 January 2015 - 10:36 AM

Code executed after each frame, ensures not keeping around unused FBO's .. a bit too efficiently smile.png

 

for (auto it = framebuffers.Begin(); it != framebuffers.End();)
{
    if (it->second->framesSinceUse > MAX_FRAMEBUFFER_AGE)
        it = framebuffers.Erase(it);
    else
        ++it->second->framesSinceUse;
}

 

 


CPU performance expectations of a renderer

01 January 2015 - 08:53 AM

Happy New Year to all! 

 

Lately I've been experimenting with trying to improve the CPU-side process of rendering preparation. For reference, I'm (still) using the simple test scene described here: http://www.yosoygames.com.ar/wp/2013/07/ogre-2-0-is-up-to-3x-faster/ which is basically 250 x 250 very simple objects and a directional light shining on them. I'm also using simple forward rendering, meaning each draw call needs to know what lights will be used for it.

 

The process boils down to:

- Frustum culling to find visible objects

- Find light interactions for visible objects

- Construct render commands from visible objects. Includes also finding out which objects can be instanced

 

Naturally the scene described in the link could be rendered extremely fast using specific code tailored just for it, but I'm talking of a generic renderer which has to expect each object possibly having different geometries, different materials and different lights shining on it.

 

My conclusion is that most of the work here is algorithmically dumb and consists mostly of shifting things around in memory, which stresses to a part the importance of cache-friendly memory access patterns.

 

I'm not fully "data-oriented" yet, meaning I still have C++ objects describing the scene objects and render commands, and there is certainly some "typical C++ bullshit" still going on. Nevertheless I've managed to speed up the CPU-side of things almost 2x compared to my previous engine (Urho3D) just by slimming down the scene objects and other data objects to the bare essentials, and ensuring there are no unnecessary passes through all the visible objects.

 

At this point I'm not necessarily looking for more performance, but I'd still like to ask for insights and experiences in this area. Is this even a significant bottleneck for you that you find worthwhile to optimize? 

 

Do you sacrifice convenient API for maximum performance? (eg. in my current design each object has a simple std::vector to list geometry and material pairs, which potentially causes an access into a "random" memory area when preparing each object's render commands. In a case where a lot of objects look the same, it would make more sense memory performance-wise to just have one std::vector and have each object point to it, but that complicates either the internal code or the API when you suddenly want some of the object(s) to look different.)

 

Or, do you utilize frame-to-frame coherency by not fully rebuilding the render command data each frame? That is currently where I spend most CPU time, so it looks like a potential large win if I could just decide that the data is same, no processing needed, but the question is how to decide that efficiently smile.png


tempVariables assertion with indexed unsafe reference

21 June 2014 - 12:58 PM

Hi,

for some time I've been unable to migrate to newer versions of AngelScript due to a tempVariables assertion I'm getting in debug mode (at least VS2008). I finally managed to make a stand-alone reproduction case. The example contains a lot of missing/stub functionality that would crash if actually run, but please don't mind that smile.png

 

The assertion I'm getting is: tempVariables.Exists(offset), file ..\..\source\as_compiler.cpp, line 4840, using the newest SVN revision. The code is also using the scriptarray add-on.

 

As far as I understand, it's the assigning to an unsafely referenced value type, that has been acquired with a nested indexing, that causes the assert.

#include <angelscript.h>
#include <string>

#include "scriptarray.h"

using namespace std;

class String
{
public:
    bool Empty() const { return true; }
};

static String StringFactory(asUINT length, const char* s)
{
    return String();
}

static void ConstructString(String* ptr)
{
    new(ptr) String();
}

static void ConstructStringCopy(const String& str, String* ptr)
{
    new(ptr) String(str);
}

static void DestructString(String* ptr)
{
    ptr->~String();
}

class ShortStringHash
{
public:
    ShortStringHash() {}
    ShortStringHash(const String& str) {}
};

static void ConstructShortStringHash(ShortStringHash* ptr)
{
    new(ptr) ShortStringHash();
}

class Variant
{
};

static void ConstructVariant(Variant* ptr)
{
    new(ptr) Variant();
}

static void ConstructVariantCopy(const Variant& variant, Variant* ptr)
{
    new(ptr) Variant(variant);
}

static void DestructVariant(Variant* ptr)
{
    ptr->~Variant();
}

class VariantMap
{
public:
    Variant& operator [] (const ShortStringHash& key) { return data; }
    Variant data;
};

static void ConstructVariantMap(VariantMap* ptr)
{
    new(ptr) VariantMap();
}

static void ConstructVariantMapCopy(const VariantMap& map, VariantMap* ptr)
{
    new(ptr) VariantMap(map);
}

static void DestructVariantMap(VariantMap* ptr)
{
    ptr->~VariantMap();
}

static Variant& VariantMapAt(const String& key, VariantMap& map)
{
    return map[key];
}

static Variant& VariantMapAtHash(ShortStringHash key, VariantMap& map)
{
    return map[key];
}

static CScriptArray* VariantMapGetKeys(const VariantMap& map)
{
    return 0;
}

class UIElement
{
public:
    void AddRef() {}
    void ReleaseRef() {}
    
    VariantMap vars;
};

static VariantMap& UIElementGetVars(UIElement* ptr)
{
    return const_cast<VariantMap&>(ptr->vars);
}

static String GetVariableName(const ShortStringHash& key)
{
    return String();
}

void MessageCallback(const asSMessageInfo* msg)
{
    printf("%s:%d,%d %s\n", msg->section, msg->row, msg->col, msg->message);
}

int main(int argc, char** argv)
{
    asIScriptEngine* engine = asCreateScriptEngine(ANGELSCRIPT_VERSION);

    engine->SetEngineProperty(asEP_USE_CHARACTER_LITERALS, true);
    engine->SetEngineProperty(asEP_ALLOW_UNSAFE_REFERENCES, true);
    engine->SetEngineProperty(asEP_ALLOW_IMPLICIT_HANDLE_TYPES, true);
    engine->SetEngineProperty(asEP_BUILD_WITHOUT_LINE_CUES, true);
    engine->SetMessageCallback(asFUNCTION(MessageCallback), 0, asCALL_CDECL);
    
    engine->RegisterObjectType("Array<class T>", 0, asOBJ_REF | asOBJ_TEMPLATE);
    engine->RegisterObjectBehaviour("Array<T>", asBEHAVE_FACTORY, "Array<T>@ f(int& in)", asFUNCTIONPR(CScriptArray::Create, (asIObjectType*), CScriptArray*), asCALL_CDECL);
    engine->RegisterObjectBehaviour("Array<T>", asBEHAVE_FACTORY, "Array<T>@ f(int& in, uint)", asFUNCTIONPR(CScriptArray::Create, (asIObjectType*, asUINT), CScriptArray*), asCALL_CDECL);
    engine->RegisterObjectBehaviour("Array<T>", asBEHAVE_FACTORY, "Array<T>@ f(int& in, uint, const T& in)", asFUNCTIONPR(CScriptArray::Create, (asIObjectType*, asUINT, void *), CScriptArray*), asCALL_CDECL);
    engine->RegisterObjectBehaviour("Array<T>", asBEHAVE_LIST_FACTORY, "Array<T>@ f(int&in type, int&in list) {repeat T}", asFUNCTIONPR(CScriptArray::Create, (asIObjectType*, void*), CScriptArray*), asCALL_CDECL);
    engine->RegisterObjectBehaviour("Array<T>", asBEHAVE_ADDREF, "void f()", asMETHOD(CScriptArray,AddRef), asCALL_THISCALL);
    engine->RegisterObjectBehaviour("Array<T>", asBEHAVE_RELEASE, "void f()", asMETHOD(CScriptArray,Release), asCALL_THISCALL);
    engine->RegisterObjectMethod("Array<T>", "Array<T>& opAssign(const Array<T>& in)", asMETHOD(CScriptArray, operator=), asCALL_THISCALL);
    engine->RegisterObjectMethod("Array<T>", "T& opIndex(uint)", asMETHODPR(CScriptArray, At, (unsigned), void*), asCALL_THISCALL);
    engine->RegisterObjectMethod("Array<T>", "const T& opIndex(uint) const", asMETHODPR(CScriptArray, At, (unsigned), void*), asCALL_THISCALL);
    engine->RegisterObjectMethod("Array<T>", "uint get_length() const", asMETHOD(CScriptArray, GetSize), asCALL_THISCALL);
    
    engine->RegisterObjectType("String", sizeof(String), asOBJ_VALUE | asOBJ_APP_CLASS_CDAK);
    engine->RegisterStringFactory("String", asFUNCTION(StringFactory), asCALL_CDECL);
    engine->RegisterObjectBehaviour("String", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(ConstructString), asCALL_CDECL_OBJLAST);
    engine->RegisterObjectBehaviour("String", asBEHAVE_CONSTRUCT, "void f(const String&in)", asFUNCTION(ConstructStringCopy), asCALL_CDECL_OBJLAST);
    engine->RegisterObjectBehaviour("String", asBEHAVE_DESTRUCT, "void f()", asFUNCTION(DestructString), asCALL_CDECL_OBJLAST);
    engine->RegisterObjectMethod("String", "String& opAssign(const String&in)", asMETHODPR(String, operator =, (const String&), String&), asCALL_THISCALL);
    engine->RegisterObjectMethod("String", "bool get_empty() const", asMETHOD(String, Empty), asCALL_THISCALL);
    
    engine->RegisterObjectType("ShortStringHash", sizeof(ShortStringHash), asOBJ_VALUE | asOBJ_POD | asOBJ_APP_CLASS_CAK);
    engine->RegisterObjectBehaviour("ShortStringHash", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(ConstructShortStringHash), asCALL_CDECL_OBJLAST);
    
    engine->RegisterObjectType("Variant", sizeof(Variant), asOBJ_VALUE | asOBJ_APP_CLASS_CDAK);
    engine->RegisterObjectBehaviour("Variant", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(ConstructVariant), asCALL_CDECL_OBJLAST);
    engine->RegisterObjectBehaviour("Variant", asBEHAVE_CONSTRUCT, "void f(const Variant&in)", asFUNCTION(ConstructVariantCopy), asCALL_CDECL_OBJLAST);
    engine->RegisterObjectBehaviour("Variant", asBEHAVE_DESTRUCT, "void f()", asFUNCTION(DestructVariant), asCALL_CDECL_OBJLAST);
    engine->RegisterObjectMethod("Variant", "Variant& opAssign(const Variant&in)", asMETHODPR(Variant, operator =, (const Variant&), Variant&), asCALL_THISCALL);
    
    engine->RegisterObjectType("VariantMap", sizeof(VariantMap), asOBJ_VALUE | asOBJ_APP_CLASS_CDAK);
    engine->RegisterObjectBehaviour("VariantMap", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(ConstructVariantMap), asCALL_CDECL_OBJLAST);
    engine->RegisterObjectBehaviour("VariantMap", asBEHAVE_CONSTRUCT, "void f(const VariantMap&in)", asFUNCTION(ConstructVariantMapCopy), asCALL_CDECL_OBJLAST);
    engine->RegisterObjectBehaviour("VariantMap", asBEHAVE_DESTRUCT, "void f()", asFUNCTION(DestructVariantMap), asCALL_CDECL_OBJLAST);
    engine->RegisterObjectMethod("VariantMap", "Array<ShortStringHash>@ get_keys() const", asFUNCTION(VariantMapGetKeys), asCALL_CDECL_OBJLAST);
    engine->RegisterObjectMethod("VariantMap", "Variant& opIndex(const String&in)", asFUNCTION(VariantMapAt), asCALL_CDECL_OBJLAST);
    engine->RegisterObjectMethod("VariantMap", "const Variant& opIndex(const String&in) const", asFUNCTION(VariantMapAt), asCALL_CDECL_OBJLAST);
    engine->RegisterObjectMethod("VariantMap", "Variant& opIndex(ShortStringHash)", asFUNCTION(VariantMapAtHash), asCALL_CDECL_OBJLAST);
    engine->RegisterObjectMethod("VariantMap", "const Variant& opIndex(ShortStringHash) const", asFUNCTION(VariantMapAtHash), asCALL_CDECL_OBJLAST);
    
    engine->RegisterObjectType("UIElement", 0, asOBJ_REF);
    engine->RegisterObjectBehaviour("UIElement", asBEHAVE_ADDREF, "void f()", asMETHODPR(UIElement, AddRef, (), void), asCALL_THISCALL);
    engine->RegisterObjectBehaviour("UIElement", asBEHAVE_RELEASE, "void f()", asMETHODPR(UIElement, ReleaseRef, (), void), asCALL_THISCALL);
    engine->RegisterObjectMethod("UIElement", "VariantMap& get_vars()", asFUNCTION(UIElementGetVars), asCALL_CDECL_OBJLAST);
    
    engine->RegisterGlobalFunction("String GetVariableName(const ShortStringHash&in)", asFUNCTION(GetVariableName), asCALL_CDECL);
    
    asIScriptModule* module = engine->GetModule("Test", asGM_ALWAYS_CREATE);
    
    string script =
        "UIElement@ element;\n"
        "VariantMap internalVars;\n"
        "void Test()\n"
        "{\n"
        "    Array<ShortStringHash> keys = element.vars.keys;\n"
        "    for (uint i = 0; i < keys.length; ++i)\n"
        "    {\n"
        "        String name = GetVariableName(keys[i]);\n"
        "        if (name.empty)\n"
        "            internalVars[keys[i]] = element.vars[keys[i]];\n"
        "    }\n"
        "}\n\n";
    printf("%s", script.c_str());
    
    module->AddScriptSection("Test", script.c_str());
    module->Build();
    
    return 0;
}

Old school 3D engines

13 February 2014 - 09:20 AM

I'd be interested to read about the very old school (pre-Wolfenstein) 3D engines used in DOS-era simulation games such as LHX Attack Chopper, for example about the data / acceleration structures they used to represent the 3D worlds. Any resources you know of? A cursory Google search did not reveal much.

 

I remember programming filled 3D graphics in the DOS days using tutorials like the PC-GPE, but at the time my understanding of things like matrices, frustum culling, proper clipping or acceleration structures were pretty much nonexistent; if I had multiple objects to draw I'd just store them to a flat array.

 

Nowadays it's common to use a quadtree or octree, but when dealing with 16-bit segmented memory, it wouldn't be that straightforward.


Assert when casting void return value to an object handle

02 November 2013 - 09:40 AM

Hi,

in the Urho3D engine we saw the following assert trigger when in script, the return value of a void function was mistakenly attempted to be cast into an object handle:

    asASSERT(ctx->type.dataType.IsReference());

in as_compiler.cpp, function asCCompiler::ConvertToVariable(asSExprContext *ctx), around line 10974.

 

To fix, I added the following check for void to asCCompiler::CompileConversion(asCScriptNode *node, asSExprContext *ctx) around line 8150:

    bool conversionOK = false;

    if( !expr.type.isConstant && expr.type.dataType.GetTokenType() != ttVoid )

which may not be the best way to fix, but seemed to work.


PARTNERS