Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feat] ImGui + ImGui Game Console #16

Draft
wants to merge 6 commits into
base: feat/compilers
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@
[submodule "thirdparty/sourcepp"]
path = thirdparty/sourcepp
url = [email protected]:craftablescience/sourcepp.git
[submodule "thirdparty/imgui"]
path = thirdparty/imgui
url = [email protected]:ocornut/imgui.git
2 changes: 2 additions & 0 deletions _cmake_scripts/groups.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ if (${BUILD_GROUP} STREQUAL "game")
include("${SRCDIR}/tier2/tier2.cmake")
include("${SRCDIR}/tier3/tier3.cmake")
include("${SRCDIR}/vgui2/vgui_controls/vgui_controls.cmake")
include("${SRCDIR}/devui/imguisystem.cmake")

elseif (${BUILD_GROUP} STREQUAL "everything")
include("${SRCDIR}/tier0/tier0.cmake")
Expand All @@ -37,6 +38,7 @@ elseif (${BUILD_GROUP} STREQUAL "everything")
include("${SRCDIR}/launcher/launcher.cmake")
include("${SRCDIR}/inputsystem/inputsystem.cmake")
include("${SRCDIR}/utils/serverplugin_sample/serverplugin_empty.cmake")
include("${SRCDIR}/devui/imguisystem.cmake")

if (${BUILD_GAME} STREQUAL "hl2mp")
include("${SRCDIR}/game/client/client_hl2mp.cmake")
Expand Down
41 changes: 41 additions & 0 deletions src/common/GameUI/IGameConsole.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//===== Copyright � 1996-2005, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//

#ifndef IGAMECONSOLE_H
#define IGAMECONSOLE_H
#ifdef _WIN32
#pragma once
#endif

#include "tier1/interface.h"


//-----------------------------------------------------------------------------
// Purpose: interface to game/dev console
//-----------------------------------------------------------------------------
abstract_class IGameConsole : public IBaseInterface
{
public:
// activates the console, makes it visible and brings it to the foreground
virtual void Activate() = 0;

virtual void Initialize() = 0;

// hides the console
virtual void Hide() = 0;

// clears the console
virtual void Clear() = 0;

// return true if the console has focus
virtual bool IsConsoleVisible() = 0;

virtual void SetParent( int parent ) = 0;
};

#define GAMECONSOLE_INTERFACE_VERSION "GameConsole004"

#endif // IGAMECONSOLE_H
64 changes: 64 additions & 0 deletions src/devui/imconfig_source.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#pragma once

//#define IMGUI_DISABLE // Disable everything: all headers and source files will be empty.

// Use tier0's asserts
#include "tier0/dbg.h"

#undef IM_ASSERT
#define IM_ASSERT Assert

// We provide our own allocators.
#define IMGUI_DISABLE_DEFAULT_ALLOCATORS


// We do not define Dear ImGui's api to cross dll boundaries.
// Instead, everything that wants to use Dear ImGui can link devui_static.lib.
#define IMGUI_API

// Disable obsolete APIs. No need to make more work for ourselves.
#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS
#define IMGUI_DISABLE_OBSOLETE_KEYIO

// Let's not link win32 for everything we include Dear ImGui with...
#define IMGUI_DISABLE_WIN32_FUNCTIONS

// We need to disable Dear ImGui's file functions, so we can pass them to Source's filesystem. Otherwise, we won't be able to access VPKs
#define IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS
using ImFileHandle = void*;
IMGUI_API ImFileHandle ImFileOpen( const char* filename, const char* mode );
IMGUI_API bool ImFileClose( ImFileHandle file );
IMGUI_API uint64 ImFileGetSize( ImFileHandle file );
IMGUI_API uint64 ImFileRead( void* data, uint64 size, uint64 count, ImFileHandle file );
IMGUI_API uint64 ImFileWrite( const void* data, uint64 size, uint64 count, ImFileHandle file );


// Source's colors are stored as BGRA. Setting this allows us to avoid per vertex swizzles in mesh builder.
// On Linux and Mac, mesh builder already does these swizzles regardless
#if !defined( OPENGL_COLOR_SWAP )
//#define IMGUI_USE_BGRA_PACKED_COLOR
#endif


//---- Use stb_sprintf.h for a faster implementation of vsnprintf instead of the one from libc (unless IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS is defined)
// Compatibility checks of arguments and formats done by clang and GCC will be disabled in order to support the extra formats provided by stb_sprintf.h.
//#define IMGUI_USE_STB_SPRINTF

#include "mathlib/vector2d.h"
#include "mathlib/vector4d.h"
#define IM_VEC2_CLASS_EXTRA \
ImVec2( const Vector2D& f ) : x( f.x ), y( f.y ) {} \
operator Vector2D() const { return Vector2D( x, y ); }

#define IM_VEC4_CLASS_EXTRA \
ImVec4( const Vector4D& f ) : x( f.x ), y( f.y ), z( f.z ), w( f.w ) {} \
operator Vector4D() const { return Vector4D( x, y, z, w ); }

#include "strtools.h"
#ifdef strncpy
#undef strncpy
#define strncpy Q_strncpy
#endif

class IMaterial;
#define ImTextureID IMaterial*
252 changes: 252 additions & 0 deletions src/devui/imgui_impl_source.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2023 Strata Source Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*********************************************************************************/
#include "cbase.h"

#include "imgui_impl_source.hpp"

#include "KeyValues.h"
#include "filesystem.h"
#include "imgui.h"
#include "imgui_internal.h"
#include "materialsystem/imesh.h"
#include "materialsystem/itexture.h"
#include "pixelwriter.h"

#include "vgui/ISystem.h"
#include "vgui_controls/Controls.h"

#include "tier0/memdbgon.h"

static IMaterial* g_pFontMat = nullptr;

class CDearImGuiFontTextureRegenerator : public ITextureRegenerator {
public:
CDearImGuiFontTextureRegenerator() = default;

// Inherited from ITextureRegenerator
void RegenerateTextureBits( ITexture* pTexture, IVTFTexture* pVTFTexture, Rect_t* pRect ) override {
ImGuiIO& io = ImGui::GetIO();
unsigned char* pixels;
int width, height;
io.Fonts->GetTexDataAsRGBA32( &pixels, &width, &height );

Assert( pVTFTexture->Width() == width );
Assert( pVTFTexture->Height() == height );
// if we ever use freetype for font loading, this should do format conversion instead
memcpy( pVTFTexture->ImageData(), pixels, 4ULL * width * height );
}

void Release() override {
delete this;
}
};

void ImGui_ImplSource_SetupRenderState( IMatRenderContext* ctx, ImDrawData* draw_data ) {
// Apply imgui's display dimensions
ctx->Viewport( draw_data->DisplayPos.x, draw_data->DisplayPos.y, draw_data->DisplaySize.x, draw_data->DisplaySize.y );

// Setup orthographic projection matrix
// Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps.
ctx->MatrixMode( MATERIAL_PROJECTION );
ctx->LoadIdentity();
ctx->Scale( 1, -1, 1 );

float L = draw_data->DisplayPos.x + 0.5f;
float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x + 0.5f;
float T = draw_data->DisplayPos.y + 0.5f;
float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y + 0.5f;
ctx->Ortho( L, T, R, B, 0.f, 1.f );

ctx->MatrixMode( MATERIAL_VIEW );
ctx->LoadIdentity();
}

void ImGui_ImplSource_RenderDrawData( ImDrawData* draw_data ) {
// Avoid rendering when minimized
if ( draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f )
return;

CMatRenderContextPtr ctx( materials );

ctx->MatrixMode( MATERIAL_VIEW );
ctx->PushMatrix();
ctx->MatrixMode( MATERIAL_PROJECTION );
ctx->PushMatrix();

ImGui_ImplSource_SetupRenderState( ctx, draw_data );

// Render command lists
ImVec2 clip_off = draw_data->DisplayPos;
for ( int n = 0; n < draw_data->CmdListsCount; n++ ) {
const ImDrawList* cmd_list = draw_data->CmdLists[ n ];
const ImDrawIdx* idx_buffer = cmd_list->IdxBuffer.Data;

// Draw the mesh
for ( int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++ ) {
const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[ cmd_i ];
if ( pcmd->UserCallback != nullptr ) {
// User callback, registered via ImDrawList::AddCallback()
// (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.)
if ( pcmd->UserCallback == ImDrawCallback_ResetRenderState )
ImGui_ImplSource_SetupRenderState( ctx, draw_data );
else
pcmd->UserCallback( cmd_list, pcmd );
} else {
if ( pcmd->GetTexID() ) {
Vector2D clipmin = { pcmd->ClipRect.x - clip_off.x, pcmd->ClipRect.y - clip_off.y };
Vector2D clipmax = { pcmd->ClipRect.z - clip_off.x, pcmd->ClipRect.w - clip_off.y };

// Avoid rendering completely clipped draws
if ( clipmax.x <= clipmin.x || clipmax.y <= clipmin.y )
continue;

ctx->SetScissorRect( clipmin.x, clipmin.y, clipmax.x, clipmax.y, true );
IMesh* mesh = ctx->GetDynamicMesh( false, nullptr, nullptr, static_cast<IMaterial*>( pcmd->GetTexID() ) );
CMeshBuilder mb;
mb.Begin( mesh, MATERIAL_TRIANGLES, cmd_list->VtxBuffer.Size, pcmd->ElemCount );

const ImDrawVert* vtx_src = cmd_list->VtxBuffer.Data;
for ( int i = 0; i < cmd_list->VtxBuffer.Size; i++ ) {
mb.Position3f( Vector2DExpand( vtx_src->pos ), 0 );
mb.Color4ubv( reinterpret_cast<const unsigned char*>( &vtx_src->col ) );
mb.TexCoord2fv( 0, &vtx_src->uv.x );
mb.AdvanceVertexF<VTX_HAVEPOS | VTX_HAVECOLOR, 1>();
vtx_src++;
}

static_cast<CIndexBuilder&>( mb ).FastIndexList( idx_buffer + pcmd->IdxOffset, 0, pcmd->ElemCount );
mb.End( false, true );
ctx->SetScissorRect( clipmin.x, clipmin.y, clipmax.x, clipmax.y, false );
}
}
}
}

ctx->MatrixMode( MATERIAL_PROJECTION );
ctx->PopMatrix();
ctx->MatrixMode( MATERIAL_VIEW );
ctx->PopMatrix();
}

bool ImGui_ImplSource_Init() {
// Setup backend capabilities flags
ImGuiIO& io = ImGui::GetIO();
io.BackendPlatformName = "source";
io.BackendRendererName = "imgui_impl_source";
io.BackendFlags = ImGuiBackendFlags_None;
io.SetClipboardTextFn = []( void*, const char* c ) {
vgui::system()->SetClipboardText( c, V_strlen( c ) );
};
io.GetClipboardTextFn = []( void* ctx ) -> const char* {
auto& g = *static_cast<ImGuiContext*>( ctx );
g.ClipboardHandlerData.clear();
auto len = vgui::system()->GetClipboardTextCount();
if ( !len )
return nullptr;
g.ClipboardHandlerData.resize( len );
vgui::system()->GetClipboardText( 0, g.ClipboardHandlerData.Data, g.ClipboardHandlerData.Size );
return g.ClipboardHandlerData.Data;
};

ImGui_ImplSource_CreateDeviceObjects();
return true;
}

void ImGui_ImplSource_Shutdown() {
ImGui_ImplSource_InvalidateDeviceObjects();
}

static bool ImGui_ImplSource_CreateFontsTexture() {
if ( g_pFontMat )
return true;

// Build texture atlas
ImGuiIO& io = ImGui::GetIO();
int width, height;
unsigned char* pixels;
io.Fonts->GetTexDataAsRGBA32( &pixels, &width, &height );

// Create a material for the texture
ITexture* fonttex = g_pMaterialSystem->CreateProceduralTexture( "imgui_font", TEXTURE_GROUP_OTHER, width, height, IMAGE_FORMAT_RGBA8888, TEXTUREFLAGS_NOMIP | TEXTUREFLAGS_POINTSAMPLE | TEXTUREFLAGS_PROCEDURAL | TEXTUREFLAGS_SINGLECOPY | TEXTUREFLAGS_NOLOD );
fonttex->SetTextureRegenerator( new CDearImGuiFontTextureRegenerator );

KeyValues* vmt = new KeyValues( "UnlitGeneric" );
vmt->SetString( "$basetexture", "imgui_font" );
vmt->SetInt( "$nocull", 1 );
vmt->SetInt( "$vertexcolor", 1 );
vmt->SetInt( "$vertexalpha", 1 );
vmt->SetInt( "$translucent", 1 );
g_pFontMat = materials->CreateMaterial( "imgui_font_mat", vmt );
g_pFontMat->AddRef();

// Store our identifier
io.Fonts->SetTexID( g_pFontMat );

return true;
}

bool ImGui_ImplSource_CreateDeviceObjects() {
return ImGui_ImplSource_CreateFontsTexture();
}

void ImGui_ImplSource_InvalidateDeviceObjects() {
if ( g_pFontMat ) {
g_pFontMat->DecrementReferenceCount();
g_pFontMat = nullptr;
}
}

// The following functions are declared in imconfig_source.h and must not be renamed

ImFileHandle ImFileOpen( const char* filename, const char* mode ) {
Assert( g_pFullFileSystem );
return g_pFullFileSystem->Open( filename, mode );
}

bool ImFileClose( ImFileHandle f ) {
if ( f == nullptr )
return false;

Assert( g_pFullFileSystem );
g_pFullFileSystem->Close( f );
return true;
}

uint64 ImFileGetSize( ImFileHandle f ) {
Assert( g_pFullFileSystem && f );

return g_pFullFileSystem->Size( f );
}

uint64 ImFileRead( void* data, uint64 sz, uint64 count, ImFileHandle f ) {
Assert( g_pFullFileSystem && f );

return g_pFullFileSystem->Read( data, sz * count, f );
}

uint64 ImFileWrite( const void* data, uint64 sz, uint64 count, ImFileHandle f ) {
Assert( g_pFullFileSystem && f );

return g_pFullFileSystem->Write( data, sz * count, f );
}
Loading