Exemplo n.º 1
0
void Spline::SetKnot(const Variant& knot, unsigned index)
{
    if (index < knots_.Size())
    {
        if (knots_.Size() > 0 && knots_[0].GetType() == knot.GetType())
            knots_[index] = knot;
        else if (knots_.Empty())
            knots_.Push(knot);
        else
            LOGERRORF("Attempted to set a Spline's Knot value of type %s where elements are already using %s", knot.GetTypeName().CString(), knots_[0].GetTypeName().CString());
    }
}
Exemplo n.º 2
0
void Spline::AddKnot(const Variant& knot, unsigned index) 
{ 
    if (index > knots_.Size())
        index = knots_.Size();

    if (knots_.Size() > 0 && knots_[0].GetType() == knot.GetType())
        knots_.Insert(index, knot);
    else if (knots_.Empty())
        knots_.Push(knot);
    else
        LOGERRORF("Attempted to add Knot to Spline of type %s where elements are already using %s", knot.GetTypeName().CString(), knots_[0].GetTypeName().CString());
}
Exemplo n.º 3
0
void AEPreferences::Read()
{
    rapidjson::Document document;

    String filepath = GetPreferencesFullPath();

    File jsonFile(context_, filepath);

    if (!jsonFile.IsOpen())
        return;

    String json;
    jsonFile.ReadText(json);

    if (!json.Length())
        return;

    if (document.Parse<0>(json.CString()).HasParseError())
    {
        LOGERRORF("Could not parse JSON data from %s", filepath.CString());
        return;
    }

    Clear();

    const Value::Member* recent_files = document.FindMember("recent_files");
    if (recent_files && recent_files->value.IsArray())
    {
        for (Value::ConstValueIterator itr = recent_files->value.Begin(); itr != recent_files->value.End(); itr++)
        {
             if (!(*itr).IsString())
                 continue;

            String path(itr->GetString());
            recentProjects_.Push(path.CString());
        }
    }

    const Value::Member* android_sdk_path = document.FindMember("android_sdk_path");
    if (android_sdk_path && android_sdk_path->value.IsString())
        androidSDKPath_ = android_sdk_path->value.GetString();

    const Value::Member* jdk_root_path = document.FindMember("jdk_root_path");
    if (jdk_root_path && jdk_root_path->value.IsString())
        jdkRootPath_ = jdk_root_path->value.GetString();

    const Value::Member* ant_path = document.FindMember("ant_path");
    if (ant_path && ant_path->value.IsString())
        antPath_ = ant_path->value.GetString();

    UpdateRecentFiles(false);

}
Exemplo n.º 4
0
bool XMLFile::BeginLoad(Deserializer& source)
{
    unsigned dataSize = source.GetSize();
    if (!dataSize && !source.GetName().Empty())
    {
        LOGERROR("Zero sized XML data in " + source.GetName());
        return false;
    }

    SharedArrayPtr<char> buffer(new char[dataSize]);
    if (source.Read(buffer.Get(), dataSize) != dataSize)
        return false;

    if (!document_->load_buffer(buffer.Get(), dataSize))
    {
        LOGERROR("Could not parse XML data from " + source.GetName());
        document_->reset();
        return false;
    }

    XMLElement rootElem = GetRoot();
    String inherit = rootElem.GetAttribute("inherit");
    if (!inherit.Empty())
    {
        // The existence of this attribute indicates this is an RFC 5261 patch file
        ResourceCache* cache = GetSubsystem<ResourceCache>();
        XMLFile* inheritedXMLFile = cache->GetResource<XMLFile>(inherit);
        if (!inheritedXMLFile)
        {
            LOGERRORF("Could not find inherited XML file: %s", inherit.CString());
            return false;
        }

        // Patch this XMLFile and leave the original inherited XMLFile as it is
        pugi::xml_document* patchDocument = document_;
        document_ = new pugi::xml_document();
        document_->reset(*inheritedXMLFile->document_);
        Patch(rootElem);
        delete patchDocument;

        // Store resource dependencies so we know when to reload/repatch when the inherited resource changes
        cache->StoreResourceDependency(this, inherit);

        // Approximate patched data size
        dataSize += inheritedXMLFile->GetMemoryUse();
    }

    // Note: this probably does not reflect internal data structure size accurately
    SetMemoryUse(dataSize);
    return true;
}
Exemplo n.º 5
0
bool BuildBase::BuildClean(const String& path)
{
    if (buildFailed_)
    {
        LOGERRORF("BuildBase::BuildClean - Attempt to clean directory of failed build, %s", path.CString());
        return false;
    }

    FileSystem* fileSystem = GetSubsystem<FileSystem>();

    if (!fileSystem->DirExists(path))
        return true;

    // On Windows, do a little dance with the folder to avoid issues
    // with deleting folder and immediately recreating it

    String pathName, fileName, ext;
    SplitPath(path, pathName, fileName, ext);
    pathName = AddTrailingSlash(pathName);    

    unsigned i = 0;
    while (true) 
    {
        String newPath = ToString("%s%s_Temp_%u", pathName.CString(), fileName.CString(), i++);
        if (!fileSystem->DirExists(newPath))
        {
            if (!MoveFileExW(GetWideNativePath(path).CString(), GetWideNativePath(newPath).CString(), MOVEFILE_WRITE_THROUGH))
            {
                FailBuild(ToString("BuildBase::BuildClean: Unable to move directory %s -> ", path.CString(), newPath.CString()));
                return false;
            }

            // Remove the moved directory
            return BuildRemoveDirectory(newPath);

        }
        else
        {
            LOGWARNINGF("BuildBase::BuildClean - temp build folder exists, removing: %s", newPath.CString());
            fileSystem->RemoveDir(newPath, true);
        }

        if (i == 255)
        {
            FailBuild(ToString("BuildBase::BuildClean: Unable to move directory ( i == 255) %s -> ", path.CString(), newPath.CString()));
            return false;
        }
    }

    return false;
}
Exemplo n.º 6
0
void XMLFile::AddAttribute(const pugi::xml_node& patch, pugi::xpath_node& original)
{
    pugi::xml_attribute attribute = patch.attribute("type");

    if (!patch.first_child() && patch.first_child().type() != pugi::node_pcdata)
    {
        LOGERRORF("XML Patch failed calling Add due to attempting to add non text to an attribute for %s.", attribute.value());
        return;
    }

    String name(attribute.value());
    name = name.Substring(1);

    pugi::xml_attribute newAttribute = original.node().append_attribute(name.CString());
    newAttribute.set_value(patch.child_value());
}
Exemplo n.º 7
0
void XMLFile::PatchAdd(const pugi::xml_node& patch, pugi::xpath_node& original)
{
    // If not a node, log an error
    if (original.attribute())
    {
        LOGERRORF("XML Patch failed calling Add due to not selecting a node, %s attribute was selected.", original.attribute().name());
        return;
    }

    // If no type add node, if contains '@' treat as attribute
    pugi::xml_attribute type = patch.attribute("type");
    if (!type || strlen(type.value()) <= 0)
        AddNode(patch, original);
    else if (type.value()[0] == '@')
        AddAttribute(patch, original);
}
bool CubemapGenerator::Render()
{

    if(!InitRender())
    {
        LOGERRORF("Unable to init render");
        return false;
    }

    GetScene()->SendEvent(E_CUBEMAPRENDERBEGIN);    
    SubscribeToEvent(E_BEGINFRAME, HANDLER(CubemapGenerator, HandleBeginFrame));
    SubscribeToEvent(E_ENDFRAME, HANDLER(CubemapGenerator, HandleEndFrame));

    return true;

}
Exemplo n.º 9
0
void BuildBase::FailBuild(const String& message)
{
    if (buildFailed_)
    {
        LOGERRORF("BuildBase::FailBuild - Attempt to fail already failed build: %s", message.CString());
        return;
    }

    buildFailed_ = true;

    BuildError(message);

    BuildSystem* buildSystem = GetSubsystem<BuildSystem>();
    buildSystem->BuildComplete(platformID_, buildPath_, false, message);

}
Exemplo n.º 10
0
bool JSONFile::ParseJSON(const String& json, JSONValue& value, bool reportError)
{
    rapidjson::Document document;
    if (document.Parse<0>(json.CString()).HasParseError())
    {
        if (reportError)
            LOGERRORF("Could not parse JSON data from string with error: %s", document.GetParseError());

        return false;
    }

    ToJSONValue(value, document);

    return true;

}
Exemplo n.º 11
0
bool AppPanini::Init(Renderer* pRenderer, RenderTarget* pRenderTarget, PaniniParameters params)
{
	ASSERT(pRenderer);
	ASSERT(pRenderTarget);

	bool bSuccess = initializeRenderingResources(pRenderer, pRenderTarget);
	if (!bSuccess)
	{
		LOGERRORF("Error initializing Panini Projection Post Process rendering resources.");
		return false;
	}

	gPaniniParametersStatic = params;
	bUsingStaticPaniniParams = true;
	
	return bSuccess;
}
Exemplo n.º 12
0
bool BuildBase::BuildCopyFile(const String& srcFileName, const String& destFileName)
{
    if (buildFailed_)
    {
        LOGERRORF("BuildBase::BuildCopyFile - Attempt to copy file of failed build, %s", srcFileName.CString());
        return false;
    }

    FileSystem* fileSystem = GetSubsystem<FileSystem>();

    bool result = fileSystem->Copy(srcFileName, destFileName);

    if (!result)
    {
        FailBuild(ToString("BuildBase::BuildCopyFile: Unable to copy file %s -> %s", srcFileName.CString(), destFileName.CString()));
        return false;
    }

    return true;
}
Exemplo n.º 13
0
JSASTProgram::JSASTProgram(const String &path, const String &json) : JSASTNode(JSAST_PROGRAM)
  , document_(new Document())
{

    if (document_->Parse<0>(json.CString()).HasParseError())
    {
        LOGERRORF("Could not parse JSON data from %s", path.CString());
        return;
    }
    else
    {
        //StringBuffer buffer;
        //PrettyWriter<StringBuffer> writer(buffer, &(document_->GetAllocator()));
        //writer.SetIndent(' ', 3);
        //document_->Accept(writer);
        //LOGINFOF("%s", buffer.GetString());
    }

    Parse(*document_);

}
Exemplo n.º 14
0
bool AppPanini::Init(Renderer* pRenderer, RenderTarget* pRenderTarget, Gui* pGuiWindow, void (*pfnPaniniToggleCallback)(bool))
#endif
{
	ASSERT(pRenderer);
	ASSERT(pRenderTarget);
	ASSERT(pGuiWindow);
	ASSERT(pfnPaniniToggleCallback);

	bool bSuccess = initializeRenderingResources(pRenderer, pRenderTarget);
	if (!bSuccess)
	{
		LOGERRORF("Error initializing Panini Projection Post Process rendering resources.");
		return false;
	}

	// UI SETTINGS
	//----------------------------------------------------------------------------------------------------------------
	PaniniParameters& params                  = gPaniniSettingsDynamic.mParams;	// shorthand
	tinystl::vector<UIProperty>& dynamicProps = gPaniniSettingsDynamic.mDynamicUIControls.mDynamicProperties;	// shorthand

	UIProperty fov("Camera Horizontal FoV", params.FoVH, 30.0f, 179.0f, 1.0f);
	addProperty(pGuiWindow, &fov);
	
	UIProperty toggle("Enable Panini Projection", gPaniniSettingsDynamic.mEnablePaniniProjection);
	addProperty(pGuiWindow, &toggle);
	
	dynamicProps.push_back(UIProperty("Panini D Parameter", params.D, 0.0f, 1.0f, 0.001f));
	dynamicProps.push_back(UIProperty("Panini S Parameter", params.S, 0.0f, 1.0f, 0.001f));
	dynamicProps.push_back(UIProperty("Screen Scale"      , params.scale, 1.0f, 10.0f, 0.01f));
	
	if (gPaniniSettingsDynamic.mEnablePaniniProjection)
	{
		gPaniniSettingsDynamic.mDynamicUIControls.ShowDynamicProperties(pGuiWindow);
	}

	gPaniniSettingsDynamic.pGui = pGuiWindow;
	g_pfnPaniniToggleCallback = pfnPaniniToggleCallback;

	return bSuccess;
}
Exemplo n.º 15
0
bool BuildBase::BuildRemoveDirectory(const String& path)
{
    if (buildFailed_)
    {
        LOGERRORF("BuildBase::BuildRemoveDirectory - Attempt to remove directory of failed build, %s", path.CString());
        return false;
    }

    FileSystem* fileSystem = GetSubsystem<FileSystem>();
    if (!fileSystem->DirExists(path))
        return true;

    bool result = fileSystem->RemoveDir(path, true);

    if (!result)
    {
        FailBuild(ToString("BuildBase::BuildRemoveDirectory: Unable to remove directory %s", path.CString()));
        return false;
    }

    return true;
}
Exemplo n.º 16
0
void openWindow(const char* app_name, WindowsDesc* winDesc)
{
	winDesc->fullscreenRect = { 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN) };

	// If user provided invalid or zero rect, get the rect from renderer
	if (getRectWidth(winDesc->windowedRect) <= 0 || getRectHeight(winDesc->windowedRect) <= 0)
	{
		getRecommendedResolution(&winDesc->windowedRect);
	}

	RECT clientRect = { (LONG)winDesc->windowedRect.left, (LONG)winDesc->windowedRect.top, (LONG)winDesc->windowedRect.right, (LONG)winDesc->windowedRect.bottom };
	AdjustWindowRect(&clientRect, WS_OVERLAPPEDWINDOW, FALSE);
	winDesc->windowedRect = { (int)clientRect.left, (int)clientRect.top, (int)clientRect.right, (int)clientRect.bottom };

	RectDesc& rect = winDesc->fullScreen ? winDesc->fullscreenRect : winDesc->windowedRect;

	WCHAR app[MAX_PATH];
	size_t charConverted = 0;
	mbstowcs_s(&charConverted, app, app_name, MAX_PATH);

	HWND hwnd = CreateWindowW(CONFETTI_WINDOW_CLASS,
		app,
		WS_OVERLAPPEDWINDOW | ((winDesc->visible) ? WS_VISIBLE : 0),
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		rect.right - rect.left,
		rect.bottom - rect.top,
		NULL,
		NULL,
		(HINSTANCE)GetModuleHandle(NULL),
		0
	);

	if (hwnd)
	{
		GetClientRect(hwnd, &clientRect);
		winDesc->windowedRect = { (int)clientRect.left, (int)clientRect.top, (int)clientRect.right, (int)clientRect.bottom };

		winDesc->handle = hwnd;
		gHWNDMap.insert({ hwnd, winDesc });

		if (winDesc->visible)
		{
			if (winDesc->maximized)
			{
				ShowWindow(hwnd, SW_MAXIMIZE);
			}
			else if (winDesc->minimized)
			{
				ShowWindow(hwnd, SW_MINIMIZE);
			}
			else if (winDesc->fullScreen)
			{
				adjustWindow(winDesc);
			}
		}

		LOGINFOF("Created window app %s", app_name);
	}
	else
	{
		LOGERRORF("Failed to create window app %s", app_name);
	}
}
Exemplo n.º 17
0
void JSBModule::Load(const String &moduleJSONFilename)
{
    ResourceCache* cache = JSBind::context_->GetSubsystem<ResourceCache>();

    JSONFile* moduleJSONFile = cache->GetResource<JSONFile>(moduleJSONFilename);

    if (!moduleJSONFile)
    {
        LOGERRORF("Couldn't load module json: %s", moduleJSONFilename.CString());
        ErrorExit("Couldn't load module json");
    }

    JSONValue moduleJSON = moduleJSONFile->GetRoot();
    JSONValue sources = moduleJSON.GetChild("sources");
    JSONValue classes = moduleJSON.GetChild("classes");
    JSONValue includes = moduleJSON.GetChild("includes");
    JSONValue classes_rename = moduleJSON.GetChild("classes_rename");
    JSONValue overloads = moduleJSON.GetChild("overloads");
    JSONValue requires = moduleJSON.GetChild("requires");

    HashMap<String, String> rename;

    if (requires.IsArray())
    {
        for (unsigned j = 0; j < requires.GetSize(); j++)
        {
            requirements_.Push(requires.GetString(j));
        }

    }

    if (classes_rename.IsObject())
    {
        Vector<String> childNames = classes_rename.GetValueNames();
        for (unsigned j = 0; j < childNames.Size(); j++)
        {
            String classname = childNames.At(j);
            String crename = classes_rename.GetString(classname);

            rename[classname] = crename;

        }

    }

    if (includes.IsArray())
    {
        for (unsigned j = 0; j < includes.GetSize(); j++)
        {
            includes_.Push(includes.GetString(j));

        }
    }

    if (classes.IsArray())
    {
        for (unsigned j = 0; j < classes.GetSize(); j++)
        {
            String classname = classes.GetString(j);

            if (rename.Contains(classname))
                bindings_->RegisterClass(classname, rename[classname]);
            else
                bindings_->RegisterClass(classname);

        }
    }

    if (overloads.IsObject())
    {
        Vector<String> childNames = overloads.GetChildNames();

        for (unsigned j = 0; j < childNames.Size(); j++)
        {
            String classname = childNames.At(j);

            JSBClass* klass = bindings_->GetClass(classname);
            if (!klass)
            {
                ErrorExit("Bad overload klass");
            }

            JSONValue classoverloads = overloads.GetChild(classname);

            Vector<String> functionNames = classoverloads.GetChildNames();

            for (unsigned k = 0; k < functionNames.Size(); k++)
            {
                JSONValue sig = classoverloads.GetChild(functionNames[k]);

                if (!sig.IsArray())
                {
                    ErrorExit("Bad overload defintion");
                }

                Vector<String> values;
                for (unsigned x = 0; x < sig.GetSize(); x++)
                {
                    values.Push(sig.GetString(x));
                }

                JSBFunctionOverride* fo = new JSBFunctionOverride(functionNames[k], values);
                klass->AddFunctionOverride(fo);

            }

        }

    }

    this->name_ = moduleJSON.GetString("name");

    if (this->name_ == "Graphics")
    {
#ifdef _MSC_VER
        sources.AddString("Graphics/Direct3D9");
#else
        sources.AddString("Graphics/OpenGL");
#endif
    }

    for (unsigned j = 0; j < sources.GetSize(); j++)
    {
        String sourceFolder = sources.GetString(j);
        Vector<String> fileNames;

        String sourceRoot = "Atomic";

        if (sourceFolder == "Javascript")
            sourceRoot = "AtomicJS";

        JSBind::fileSystem_->ScanDir(fileNames, JSBind::ROOT_FOLDER + "/Source/" + sourceRoot + "/" + sourceFolder, "*.h", SCAN_FILES, false);
        for (unsigned k = 0; k < fileNames.Size(); k++)
        {
            // TODO: filter
            String filepath = JSBind::ROOT_FOLDER + "/Source/" + sourceRoot + "/" + sourceFolder + "/" + fileNames[k];

            this->headerFiles_.Push(filepath);
        }

    }

}
Exemplo n.º 18
0
bool JSBModule::Load(const String& jsonFilename)
{
    JSBind* jsbind = GetSubsystem<JSBind>();

    LOGINFOF("Loading Module: %s", jsonFilename.CString());

    SharedPtr<File> jsonFile(new File(context_, jsonFilename));

    if (!jsonFile->IsOpen())
    {
        LOGERRORF("Unable to open module json: %s", jsonFilename.CString());
        return false;
    }

    moduleJSON_ = new JSONFile(context_);

    if (!moduleJSON_->BeginLoad(*jsonFile))
    {
        LOGERRORF("Unable to parse module json: %s", jsonFilename.CString());
        return false;
    }

    JSONValue root = moduleJSON_->GetRoot();

    name_ = root.GetString("name");

    JSONValue requires = root.GetChild("requires");

    if (requires.IsArray())
    {
        for (unsigned j = 0; j < requires.GetSize(); j++)
        {
            requirements_.Push(requires.GetString(j));
        }

    }

    JSONValue classes = root.GetChild("classes");

    for (unsigned i = 0; i < classes.GetSize(); i++)
    {
        classnames_.Push(classes.GetString(i));
    }

    JSONValue classes_rename = root.GetChild("classes_rename");

    if (classes_rename.IsObject())
    {
        Vector<String> childNames = classes_rename.GetValueNames();
        for (unsigned j = 0; j < childNames.Size(); j++)
        {
            String classname = childNames.At(j);
            String crename = classes_rename.GetString(classname);

            classRenames_[classname] = crename;

        }

    }

    JSONValue includes = root.GetChild("includes");

    if (includes.IsArray())
    {
        for (unsigned j = 0; j < includes.GetSize(); j++)
        {
            includes_.Push(includes.GetString(j));

        }
    }

    JSONValue sources = root.GetChild("sources");

    for (unsigned i = 0; i < sources.GetSize(); i++)
    {
        sourceDirs_.Push(sources.GetString(i));
    }

    if (name_ == "Graphics")
    {
#ifdef _MSC_VER
        if (jsbind->GetPlatform() == "ANDROID" || jsbind->GetPlatform() == "WEB")
        {
            sourceDirs_.Push("Source/Atomic/Graphics/OpenGL");
        }
        else
        {
#ifdef ATOMIC_D3D11
            sourceDirs_.Push("Source/Atomic/Graphics/Direct3D11");
#else
            sourceDirs_.Push("Source/Atomic/Graphics/Direct3D9");
#endif
        }
#else
        sourceDirs_.Push("Source/Atomic/Graphics/OpenGL");
#endif
    }


    ScanHeaders();

    return true;
}
Exemplo n.º 19
0
void LicenseSystem::HandleVerification(StringHash eventType, VariantMap& eventData)
{

    CurlRequest* request = (CurlRequest*) (eventData[CurlComplete::P_CURLREQUEST].GetPtr());

    if (serverVerification_.NotNull())
    {
        assert(request == serverVerification_);

        if (serverVerification_->GetError().Length())
        {
            LOGERRORF("Unable to verify with server: %s", serverVerification_->GetError().CString());
        }
        else
        {
            LicenseParse parse;
            int code = ParseResponse(serverVerification_->GetResponse(), parse);

            // Not activated
            if (code == 4)
            {

            }
            else if (code == 2)
            {
                // something is wrong with the key
                LOGERRORF("Error with product key");

                RemoveLicense();
                ResetLicense();
                UIModalOps* ops = GetSubsystem<UIModalOps>();
                ops->Hide();
                ops->ShowActivation();

            }
            else if (code == 3)
            {
                // something is wrong on the activation server
                key_ = "";
            }
            else if (code == 1)
            {
                // exceeded code, should not happen here as we aren't activating
                key_ = "";
            }
            else if (code == 0)
            {
                // we should raise an error if there is a mismatch between local and server keys
                // when the local says there are more enabled than server?
                // otherwise, they could be being added

                bool mismatch = false;

                if (parse.licenseWindows_ != licenseWindows_)
                    mismatch = true;

                if (parse.licenseMac_ != licenseMac_)
                    mismatch = true;

                if (parse.licenseWindows_ != licenseWindows_)
                    mismatch = true;

                if (parse.licenseAndroid_ != licenseAndroid_)
                    mismatch = true;

                if (parse.licenseIOS_ != licenseIOS_)
                    mismatch = true;

                if (parse.licenseHTML5_ != licenseHTML5_)
                    mismatch = true;

                if (parse.licenseModule3D_ != licenseModule3D_)
                    mismatch = true;

                if (mismatch)
                {
                    LOGERROR("License Mismatch, reseting");
                    licenseWindows_ = parse.licenseWindows_;
                    licenseMac_ = parse.licenseMac_;
                    licenseAndroid_ = parse.licenseAndroid_;
                    licenseIOS_= parse.licenseIOS_;
                    licenseHTML5_= parse.licenseHTML5_;
                    licenseModule3D_= parse.licenseModule3D_;

                    SaveLicense();
                }

                //if (!HasPlatformLicense())
                //{
                //    UIModalOps* ops = GetSubsystem<UIModalOps>();
                //    if (!ops->ModalActive())
                //        ops->ShowPlatformsInfo();

               // }

            }

        }

        UnsubscribeFromEvents(serverVerification_);
        serverVerification_ = 0;
    }

}
Exemplo n.º 20
0
bool ShaderProgram::Link()
{
    PROFILE(LinkShaderProgram);

    Release();

    if (!graphics || !graphics->IsInitialized())
    {
        LOGERROR("Can not link shader program without initialized Graphics subsystem");
        return false;
    }
    if (!vs || !ps)
    {
        LOGERROR("Shader(s) are null, can not link shader program");
        return false;
    }
    if (!vs->GLShader() || !ps->GLShader())
    {
        LOGERROR("Shaders have not been compiled, can not link shader program");
        return false;
    }

    const String& vsSourceCode = vs->Parent() ? vs->Parent()->SourceCode() : String::EMPTY;
    const String& psSourceCode = ps->Parent() ? ps->Parent()->SourceCode() : String::EMPTY;

    program = glCreateProgram();
    if (!program)
    {
        LOGERROR("Could not create shader program");
        return false;
    }

    glAttachShader(program, vs->GLShader());
    glAttachShader(program, ps->GLShader());
    glLinkProgram(program);

    int linked;
    glGetProgramiv(program, GL_LINK_STATUS, &linked);
    if (!linked)
    {
        int length, outLength;
        String errorString;

        glGetProgramiv(program, GL_INFO_LOG_LENGTH, &length);
        errorString.Resize(length);
        glGetProgramInfoLog(program, length, &outLength, &errorString[0]);
        glDeleteProgram(program);
        program = 0;

        LOGERRORF("Could not link shaders %s: %s", FullName().CString(), errorString.CString());
        return false;
    }

    LOGDEBUGF("Linked shaders %s", FullName().CString());

    glUseProgram(program);

    char nameBuffer[MAX_NAME_LENGTH];
    int numAttributes, numUniforms, numUniformBlocks, nameLength, numElements;
    GLenum type;

    attributes.Clear();

    glGetProgramiv(program, GL_ACTIVE_ATTRIBUTES, &numAttributes);
    for (int i = 0; i < numAttributes; ++i)
    {
        glGetActiveAttrib(program, i, (GLsizei)MAX_NAME_LENGTH, &nameLength, &numElements, &type, nameBuffer);

        VertexAttribute newAttribute;
        newAttribute.name = String(nameBuffer, nameLength);
        newAttribute.semantic = SEM_POSITION;
        newAttribute.index = 0;

        for (size_t j = 0; elementSemanticNames[j]; ++j)
        {
            if (newAttribute.name.StartsWith(elementSemanticNames[j], false))
            {
                int index = NumberPostfix(newAttribute.name);
                if (index >= 0)
                    newAttribute.index = (unsigned char)index;
                break;
            }
            newAttribute.semantic = (ElementSemantic)(newAttribute.semantic + 1);
        }

        if (newAttribute.semantic == MAX_ELEMENT_SEMANTICS)
        {
            LOGWARNINGF("Found vertex attribute %s with no known semantic in shader program %s", newAttribute.name.CString(), FullName().CString());
            continue;
        }

        newAttribute.location = glGetAttribLocation(program, newAttribute.name.CString());
        attributes.Push(newAttribute);
    }

    glGetProgramiv(program, GL_ACTIVE_UNIFORMS, &numUniforms);
    int numTextures = 0;
    for (int i = 0; i < numUniforms; ++i)
    {
        glGetActiveUniform(program, i, MAX_NAME_LENGTH, &nameLength, &numElements, &type, nameBuffer);

        String name(nameBuffer, nameLength);
        if (type >= GL_SAMPLER_1D && type <= GL_SAMPLER_2D_SHADOW)
        {
            // Assign sampler uniforms to a texture unit according to the number appended to the sampler name
            int location = glGetUniformLocation(program, name.CString());
            int unit = NumberPostfix(name);
            // If no unit number specified, assign in appearance order starting from unit 0
            if (unit < 0)
                unit = numTextures;

            // Array samplers may have multiple elements, assign each sequentially
            if (numElements > 1)
            {
                Vector<int> units;
                for (int j = 0; j < numElements; ++j)
                    units.Push(unit++);
                glUniform1iv(location, numElements, &units[0]);
            }
            else
                glUniform1iv(location, 1, &unit);

            numTextures += numElements;
        }
    }

    glGetProgramiv(program, GL_ACTIVE_UNIFORM_BLOCKS, &numUniformBlocks);
    for (int i = 0; i < numUniformBlocks; ++i)
    {
        glGetActiveUniformBlockName(program, i, (GLsizei)MAX_NAME_LENGTH, &nameLength, nameBuffer);

        // Determine whether uniform block belongs to vertex or pixel shader
        String name(nameBuffer, nameLength);
        bool foundVs = vsSourceCode.Contains(name);
        bool foundPs = psSourceCode.Contains(name);
        if (foundVs && foundPs)
        {
            LOGWARNINGF("Found uniform block %s in both vertex and pixel shader in shader program %s");
            continue;
        }

        // Vertex shader constant buffer bindings occupy slots starting from zero to maximum supported, pixel shader bindings
        // from that point onward
        unsigned blockIndex = glGetUniformBlockIndex(program, name.CString());

        int bindingIndex = NumberPostfix(name);
        // If no number postfix in the name, use the block index
        if (bindingIndex < 0)
            bindingIndex = blockIndex;
        if (foundPs)
            bindingIndex += (unsigned)graphics->NumVSConstantBuffers();

        glUniformBlockBinding(program, blockIndex, bindingIndex);
    }

    return true;
}