bs::framework

Next-generation C++ game development framework focused on modern technologies, quality design and high performance

Get latest (Windows x64, v1.0 BETA)

See all releases | View source on GitHub | Full documentation

Support the project

This is a free & open-source project.
Consider donating so we can keep adding awesome new features.

Highlights

One neat package
bs::f provides everything you need for development of graphical applications, ranging from audio, animation, GUI, input, physics, rendering to scripting systems. It also comes with built-in support for over 30 image, mesh and audio formats, as well as a wide range of utility systems, from an extensive math library, run-time type information, CPU/GPU profiling, SIMD instruction API and a lot more. Cross-platform on Windows, Linux and macOS.

Clean C++14 API
All of the framework's functionality is provided through a neatly designed, modern C++14 API. It is simple to understand and use, while offering a lot of advanced options for those that require them. Every non-trivial method is documented in an extensive API reference. Over a hundred manuals are provided guiding you through all major systems from start to finish, with extensive amount of code snippets, as well as fully functional examples.

Code snippets (High level API)

Asset import

// Import some assets
HMesh myModel = gImporter().import<Mesh>("MyModel.fbx");
HTexture myTexture = gImporter().import<Texture>("MyTexture.png");
HAudioClip myAudioClip = gImporter().import<AudioClip>("MyAudioClip.ogg");

Shaders & materials

// Grab the default PBR shader
HShader myShader = gBuiltinResources().getBuiltinShader(BuiltinShader::Standard);

// Or import your own
// HShader myShader = gImporter().import<Shader>("CustomShader.bsl");

// Create material and assign a texture to it
HMaterial myMaterial = Material::create(myShader);
myShader->setTexture("gAlbedoTex", myTexture);

Render an object

// Add a camera to view the world through
HSceneObject cameraSO = SceneObject::create("Camera");
HCamera camera = cameraSO->addComponent<CCamera>();

// Position the camera 10 units along the Z axis, looking towards the origin
cameraSO->setPosition(Vector3(0, 0, 10.0f));
cameraSO->lookAt(Vector3(0, 0, 0));

// Add a 3D object to render
HSceneObject renderableSO = SceneObject::create("Renderable");
HRenderable renderable = renderableSO->addComponent<CRenderable>();

// Assign a 3D model and a material to render it with
renderable->setMesh(myModel);
renderable->setMaterial(myMaterial);

Physics

// Add a character controller for moving the camera
HCharacterController charController =
    cameraSO->addComponent<CCharacterController>();

// Make the character about 1.8m high, with 0.4m radius
// (controller represents a capsule)
charController->setHeight(1.0f);
charController->setRadius(0.4f);

// Rigidbody & collider to make our 3D object interactable
HRigidbody rigidbody = renderableSO->addComponent<CRigidbody>();
HBoxCollider collider = renderableSO->addComponent<CBoxCollider>();
collider->setExtents(Vector3(0.7f, 2.0f, 0.7f));

Input

// Get current transform of the player's object
const Transform& tfrm = cameraSO->getTransform();

// Determine movement of the object in four directions, relative to the
// current orientation
Vector3 direction = Vector3::ZERO;
if(gInput().isButtonHeld(BC_W)) direction += tfrm.getForward();
if(gInput().isButtonHeld(BC_S)) direction -= tfrm.getForward();
if(gInput().isButtonHeld(BC_D)) direction += tfrm.getRight();
if(gInput().isButtonHeld(BC_A)) direction -= tfrm.getRight();

// Perform the movement
charController->move(direction * gTime().getFrameDelta());

Audio

// Add a listener to the player's (camera's) position
HAudioListener listener = cameraSO->addComponent<CAudioListener>();

// Add an audio source and assign a clip to play
HSceneObject audioSourceSO = SceneObject::create("AudioSource");
HAudioSource audioSource = audioSourceSO->addComponent<CAudioSource>();
audioSource->setClip(myAudioClip);

GUI

// Add a GUI widget to serve as a root GUI object
HSceneObject guiSO = SceneObject::create("GUI");
HGUIWidget gui = guiSO->addComponent<CGUIWidget>(camera);

// Get a panel we can attach some elements to
GUIPanel* guiPanel = gui->getPanel();

// Add a layout for auto-formatting
GUILayout* guiLayout = guiPanel->addNewElement<GUILayoutY>();

// Add a couple of buttons to the layout (using the default GUI skin)
guiLayout->addNewElement<GUIButton>(HString("Click me!"));
guiLayout->addNewElement<GUIButton>(HString("Click me too!"));
Code snippets (Rendering API)

Create a GPU program

// Create a fragment GPU program from HLSL code
const char* fragProgSrc = "HLSL code goes here...";

GPU_PROGRAM_DESC fragProgDesc;
fragProgDesc.type = GPT_FRAGMENT_PROGRAM;
fragProgDesc.entryPoint = "main";
fragProgDesc.language = "hlsl";
fragProgDesc.source = fragProgSrc;

SPtr<GpuProgram> fragProg = GpuProgram::create(fragProgDesc);

Create a graphics pipeline state

// Enable blending
BLEND_STATE_DESC blendDesc;
blendDesc.renderTargetDesc[0].blendEnable = true;
blendDesc.renderTargetDesc[0].renderTargetWriteMask = 0b0111;
blendDesc.renderTargetDesc[0].blendOp = BO_ADD;
blendDesc.renderTargetDesc[0].srcBlend = BF_SOURCE_ALPHA;
blendDesc.renderTargetDesc[0].dstBlend = BF_INV_SOURCE_ALPHA;

// No depth writes or reads
DEPTH_STENCIL_STATE_DESC depthStencilDesc;
depthStencilDesc.depthWriteEnable = false;
depthStencilDesc.depthReadEnable = false;

// Create the pipeline state using GPU programs and a set of fixed states
PIPELINE_STATE_DESC pipelineDesc;
pipelineDesc.blendState = BlendState::create(blendDesc);
pipelineDesc.depthStencilState = DepthStencilState::create(depthStencilDesc);
pipelineDesc.vertexProgram = vertProg;
pipelineDesc.fragmentProgram = fragProg;

SPtr<GraphicsPipelineState> pipelineState =
    GraphicsPipelineState::create(pipelineDesc);

Create a texture

 // Create a 1024x1024 texture using 32-bit floating point format
TEXTURE_DESC desc;
desc.type = TEX_TYPE_2D;
desc.format = PF_R32F;
desc.width = 1024;
desc.height = 1024;

SPtr<Texture> texture = Texture::create(desc);

// Write some data to the texture
PixelData pixelData = texture->lock(GBL_WRITE_ONLY_DISCARD);

for (UINT32 y = 0; y < desc.height; y++)
    for (UINT32 x = 0; x < desc.width; x++)
        pixelData.setColorAt(Color::WHITE, x, y);

texture->unlock();

Create a vertex buffer

// Create a vertex declaration for shader inputs
SPtr<VertexDataDesc> vertexDesc = VertexDataDesc::create();
vertexDesc->addVertElem(VET_FLOAT3, VES_POSITION);
vertexDesc->addVertElem(VET_FLOAT2, VES_TEXCOORD);

SPtr<VertexDeclaration> vertexDecl = VertexDeclaration::create(vertexDesc);

// Create the vertex buffer object
UINT32 vertexStride = vertexDesc->getVertexStride();

VERTEX_BUFFER_DESC vbDesc;
vbDesc.numVerts = NUM_VERTICES;
vbDesc.vertexSize = vertexStride;

SPtr<VertexBuffer> vertexBuffer = VertexBuffer::create(vbDesc);

// Populate the vertex buffer with vertex information
UINT8* vbData = (UINT8*)vertexBuffer->lock(0, vertexStride * NUM_VERTICES, GBL_WRITE_ONLY_DISCARD);
UINT8* positions = vbData + vertexDesc->getElementOffsetFromStream(VES_POSITION);
UINT8* uvs = vbData + vertexDesc->getElementOffsetFromStream(VES_TEXCOORD);

AABox box(Vector3::ONE * -10.0f, Vector3::ONE * 10.0f);
writeBoxVertices(box, positions, uvs, vertexStride); // Code not shown

vertexBuffer->unlock();

Create a render surface

// Create a color attachment texture for the render surface
TEXTURE_DESC colorAttDesc;
colorAttDesc.width = windowResWidth;
colorAttDesc.height = windowResHeight;
colorAttDesc.format = PF_RGBA8;
colorAttDesc.usage = TU_RENDERTARGET;

SPtr<Texture> colorAtt = Texture::create(colorAttDesc);

// Create a depth attachment texture for the render surface
TEXTURE_DESC depthAttDesc;
depthAttDesc.width = windowResWidth;
depthAttDesc.height = windowResHeight;
depthAttDesc.format = PF_D32;
depthAttDesc.usage = TU_DEPTHSTENCIL;

SPtr<Texture> depthAtt = Texture::create(depthAttDesc);

// Create the render surface
RENDER_TEXTURE_DESC desc;
desc.colorSurfaces[0].texture = colorAtt;
desc.depthStencilSurface.texture = depthAtt;

SPtr<RenderTarget> renderTarget = RenderTexture::create(desc);

Drawing

// Get the primary render API access point
RenderAPI& rapi = RenderAPI::instance();

// Bind render surface & clear it
rapi.setRenderTarget(renderTarget);
rapi.clearRenderTarget(FBT_COLOR | FBT_DEPTH);

// Bind the pipeline state
rapi.setGraphicsPipeline(pipelineState);

// Set the vertex & index buffers, as well as vertex declaration and draw type
rapi.setVertexBuffers(0, vertexBuffer.get(), 1);
rapi.setIndexBuffer(indexBuffer);
rapi.setVertexDeclaration(vertexDecl);
rapi.setDrawOperation(DOT_TRIANGLE_LIST);

// Bind the GPU program parameters
rapi.setGpuParams(gpuParams);

// Draw
rapi.drawIndexed(0, NUM_INDICES, 0, NUM_VERTICES, 1);

Unified shading language (BSL)

shader MyShader
{
    // No depth reads or writes
    depth
    {
        read = false;
        write = false;
    };

    // Enable blending on the first render target
    blend
    {
        target
        {
            enabled = true;
            color = { srcA, srcIA, add };
            writemask = RGB;
        };
    };

    // Disable culling
    raster
    {
        cull = none;
    };

    // Only render if stencil test passes
    stencil
    {
        enabled = true;
        front = { keep, keep, keep, lte };
        reference = 1;
    };

    // Render the geometry in plain white
    code
    {
        struct VStoFS
        {
            float4 position : SV_Position;
        };

        cbuffer PerObject
        {
            float4x4 gMatWorldViewProj;
        }

        struct VertexInput
        {
            float3 position : POSITION;
        };

        VStoFS vsmain(VertexInput input)
        {
            VStoFS output;
            output.position = mul(gMatWorldViewProj, input.position);
            return output;
        }

        float4 fsmain(in VStoFS input) : SV_Target0
        {
            return float4(1.0f, 1.0f, 1.0f 1.0f);
        }
    };
};

Modern rendering API
The framework comes with a low-level rendering API based on Vulkan. It offers extensive support for all modern rendering features, including compute and tesselation shaders, instanced rendering, texture arrays, buffers, unordered access reads and writes, as well as full API support for pipeline state objects, command lists, explicit multi-GPU and async compute/upload queues. The framework also comes with DirectX 11 and OpenGL 4.5 render backends, and is easily extensible to others. Full support for both GLSL and HLSL is provided, including reflection as well as bytecode caching.

Unified shading language
BSL is a high-level shading language based on HLSL, offering a very familiar syntax with a variety of high level features that make shader development easier. Some of the enhancements include the ability to define non-programmable states like blend, rasterizer or depth-stencil in the shader code itself. Mixins allow you to provide re-usable pieces of shader code that can be included in other more complex shaders. Variations allow you to easily declare multiple permutations of the same shader. BSL cross-compiles to HLSL and GLSL ensuring you can use the same shaders for Vulkan, OpenGL or DirectX.

Physically based renderer
The framework comes with a gamma-correct, HDR enabled renderer with support for physically based shading models, as well as the ability to define custom surface and lighting models. The renderer is a hybrid of tiled deferred and clustered forward algorithms, utilizing compute capabilities for fast rendering with many lights. It supports punctual and area lights with soft shadows, reflection probes with geometry proxies, indirect lighting via irradiance maps, screen space reflections and many more features.

Multi-threaded and high performance
The framework was designed so it can satisfy the performance needs of even the most demanding projects. All major systems are multi-threaded, including animation, audio, physics and most importantly the renderer. Performance critical systems are well optimized with data organized in cache friendly structures, with the use of CPU vector instructions where applicable. Custom memory allocators ensure minimal memory allocation overhead and fragmentation. Built-in systems for CPU, GPU and memory profiling are provided.

Extensible
Built with advanced users in mind, with internals that can be easily tweaked and enhanced with new systems, ensuring it is suitable for projects with highly specialized features as well as to serve as a quality foundation for higher level frameworks, engines or tools. Offers a clean, modular architecture that is easy to follow and modify. Implementations are lightweight without the crud often found in older, larger solutions. The internals are fully documented and come with a set of manuals guiding you how to extend various aspects of the core.

Get started

User manuals

Visit the documentation and start reading the user manuals. They will show you how to use the framework from the ground up, including how to get started with your own project.

Visit
Examples

Take a look at working examples utilizing the framework. Analyze and tweak them in order to learn, or even expand them into your own project! Take this option if you want a quick start.

Visit

Supported by