This commit is contained in:
1 parent
039dcaa381
commit
4072fea1cc
6 files changed
+1422
-33
No files matched your search
+298
-24
@@ -211,7 +211,8 @@ begin
|
|||||||
and Assigned(glTexParameteri)
|
and Assigned(glTexParameteri)
|
||||||
and Assigned(glTexImage2D)
|
and Assigned(glTexImage2D)
|
||||||
and Assigned(glGetIntegerv)
|
and Assigned(glGetIntegerv)
|
||||||
and Assigned(glPixelStorei)) then
|
and Assigned(glPixelStorei)
|
||||||
|
and Assigned(CastleGLES.glGetError)) then
|
||||||
raise Exception.Create('Texture backend initialization failed');
|
raise Exception.Create('Texture backend initialization failed');
|
||||||
gTextureBackendReady := True;
|
gTextureBackendReady := True;
|
||||||
except
|
except
|
||||||
@@ -221,6 +222,50 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
function DrainTextureGLErrors: string;
|
||||||
|
var
|
||||||
|
ErrorCode: CastleGLES.GLenum;
|
||||||
|
begin
|
||||||
|
Result := '';
|
||||||
|
repeat
|
||||||
|
ErrorCode := CastleGLES.glGetError();
|
||||||
|
if ErrorCode <> CastleGLES.GL_NO_ERROR then
|
||||||
|
begin
|
||||||
|
if Result <> '' then Result := Result + ', ';
|
||||||
|
Result := Result + '0x' + IntToHex(ErrorCode, 8);
|
||||||
|
end;
|
||||||
|
until ErrorCode = CastleGLES.GL_NO_ERROR;
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
procedure RequireTextureNoGLError(const aOperation, aStage: string);
|
||||||
|
var
|
||||||
|
Errors: string;
|
||||||
|
begin
|
||||||
|
Errors := DrainTextureGLErrors;
|
||||||
|
if Errors <> '' then
|
||||||
|
begin
|
||||||
|
KMRenderMobileInvalidateTextureBindingCache;
|
||||||
|
raise Exception.Create(Format('texture %s — current GLES error at %s: %s',
|
||||||
|
[aOperation, aStage, Errors]));
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
procedure RequireNoPreexistingTextureGLError(const aOperation: string);
|
||||||
|
var
|
||||||
|
Errors: string;
|
||||||
|
begin
|
||||||
|
Errors := DrainTextureGLErrors;
|
||||||
|
if Errors <> '' then
|
||||||
|
begin
|
||||||
|
KMRenderMobileInvalidateTextureBindingCache;
|
||||||
|
raise Exception.Create(Format('texture %s — pre-existing GLES error(s): %s',
|
||||||
|
[aOperation, Errors]));
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
procedure KMRenderMobileBindTexture(aTexId: Cardinal);
|
procedure KMRenderMobileBindTexture(aTexId: Cardinal);
|
||||||
begin
|
begin
|
||||||
RequireTextureBackendReady;
|
RequireTextureBackendReady;
|
||||||
@@ -243,14 +288,90 @@ var
|
|||||||
alphaData: array of Byte;
|
alphaData: array of Byte;
|
||||||
pixelCount, I: Cardinal;
|
pixelCount, I: Cardinal;
|
||||||
unpackAlignment: CastleGLES.GLint;
|
unpackAlignment: CastleGLES.GLint;
|
||||||
restoreUnpackAlignment: Boolean;
|
alignmentKnown, alignmentSetAttempted, bindAttempted: Boolean;
|
||||||
|
operationUnverified: Boolean;
|
||||||
|
finalizeErrors, finalizeEntryErrors: string;
|
||||||
|
|
||||||
|
procedure AddFinalizeError(const aStage, aMessage: string);
|
||||||
begin
|
begin
|
||||||
|
if finalizeErrors <> '' then finalizeErrors := finalizeErrors + '; ';
|
||||||
|
finalizeErrors := finalizeErrors + aStage + ': ' + aMessage;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure CollectFinalizePending(const aStage: string);
|
||||||
|
var
|
||||||
|
Errors: string;
|
||||||
|
begin
|
||||||
|
if not gTextureBackendReady then Exit;
|
||||||
|
try
|
||||||
|
Errors := DrainTextureGLErrors;
|
||||||
|
if Errors <> '' then AddFinalizeError(aStage, Errors);
|
||||||
|
except
|
||||||
|
on E: Exception do AddFinalizeError(aStage, E.Message);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure FinalizeUpdate(aCheckPending: Boolean);
|
||||||
|
begin
|
||||||
|
finalizeEntryErrors := '';
|
||||||
|
if aCheckPending and operationUnverified and gTextureBackendReady then
|
||||||
|
try
|
||||||
|
finalizeEntryErrors := DrainTextureGLErrors;
|
||||||
|
if finalizeEntryErrors <> '' then
|
||||||
|
AddFinalizeError('update/finalize-entry', finalizeEntryErrors);
|
||||||
|
except
|
||||||
|
on E: Exception do
|
||||||
|
AddFinalizeError('update/finalize-entry', E.Message);
|
||||||
|
end;
|
||||||
|
|
||||||
|
if bindAttempted then
|
||||||
|
if gTextureBackendReady then
|
||||||
|
try
|
||||||
|
operationUnverified := True;
|
||||||
|
KMRenderMobileBindTexture(0);
|
||||||
|
RequireTextureNoGLError('update', 'unbind');
|
||||||
|
operationUnverified := False;
|
||||||
|
except
|
||||||
|
on E: Exception do
|
||||||
|
begin
|
||||||
|
operationUnverified := False;
|
||||||
|
AddFinalizeError('update/unbind', E.Message);
|
||||||
|
CollectFinalizePending('update/unbind');
|
||||||
|
end;
|
||||||
|
end
|
||||||
|
else
|
||||||
|
AddFinalizeError('update/unbind', 'current texture context is unavailable');
|
||||||
|
|
||||||
|
if alignmentKnown and alignmentSetAttempted and (unpackAlignment <> 1) then
|
||||||
|
if gTextureBackendReady then
|
||||||
|
try
|
||||||
|
operationUnverified := True;
|
||||||
|
CastleGLES.glPixelStorei(CastleGLES.GL_UNPACK_ALIGNMENT, unpackAlignment);
|
||||||
|
RequireTextureNoGLError('update', 'restore-unpack');
|
||||||
|
operationUnverified := False;
|
||||||
|
except
|
||||||
|
on E: Exception do
|
||||||
|
begin
|
||||||
|
operationUnverified := False;
|
||||||
|
AddFinalizeError('update/restore-unpack', E.Message);
|
||||||
|
CollectFinalizePending('update/restore-unpack');
|
||||||
|
end;
|
||||||
|
end
|
||||||
|
else
|
||||||
|
AddFinalizeError('update/restore-unpack', 'current texture context is unavailable');
|
||||||
|
end;
|
||||||
|
|
||||||
|
begin
|
||||||
|
try
|
||||||
RequireTextureBackendReady;
|
RequireTextureBackendReady;
|
||||||
if (aTexture = 0) or (aWidth = 0) or (aHeight = 0)
|
if (aTexture = 0) or (aWidth = 0) or (aHeight = 0)
|
||||||
or not IsPowerOfTwo(aWidth) or not IsPowerOfTwo(aHeight)
|
or not IsPowerOfTwo(aWidth) or not IsPowerOfTwo(aHeight)
|
||||||
or (aData = nil) then
|
or (aData = nil) then
|
||||||
raise Exception.Create('Invalid texture upload parameters');
|
raise Exception.Create('Invalid texture upload parameters');
|
||||||
|
if not (aMode in [tfRGB5A1, tfRGBA8, tfAlpha8]) then
|
||||||
|
raise Exception.Create('Unsupported texture upload format');
|
||||||
|
|
||||||
|
RequireNoPreexistingTextureGLError('update');
|
||||||
if aMode = tfAlpha8 then
|
if aMode = tfAlpha8 then
|
||||||
begin
|
begin
|
||||||
pixelCount := Cardinal(aWidth) * Cardinal(aHeight);
|
pixelCount := Cardinal(aWidth) * Cardinal(aHeight);
|
||||||
@@ -259,45 +380,111 @@ begin
|
|||||||
alphaData[I] := Byte((PCardinal(aData)[I] shr 24) and $FF);
|
alphaData[I] := Byte((PCardinal(aData)[I] shr 24) and $FF);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
alignmentKnown := False;
|
||||||
|
alignmentSetAttempted := False;
|
||||||
|
bindAttempted := False;
|
||||||
|
operationUnverified := False;
|
||||||
|
finalizeErrors := '';
|
||||||
|
try
|
||||||
|
operationUnverified := True;
|
||||||
CastleGLES.glGetIntegerv(CastleGLES.GL_UNPACK_ALIGNMENT, @unpackAlignment);
|
CastleGLES.glGetIntegerv(CastleGLES.GL_UNPACK_ALIGNMENT, @unpackAlignment);
|
||||||
restoreUnpackAlignment := unpackAlignment <> 1;
|
RequireTextureNoGLError('update', 'glGetIntegerv');
|
||||||
if restoreUnpackAlignment then
|
operationUnverified := False;
|
||||||
|
alignmentKnown := True;
|
||||||
|
|
||||||
|
if unpackAlignment <> 1 then
|
||||||
|
begin
|
||||||
|
alignmentSetAttempted := True;
|
||||||
|
operationUnverified := True;
|
||||||
CastleGLES.glPixelStorei(CastleGLES.GL_UNPACK_ALIGNMENT, 1);
|
CastleGLES.glPixelStorei(CastleGLES.GL_UNPACK_ALIGNMENT, 1);
|
||||||
try
|
RequireTextureNoGLError('update', 'glPixelStorei(1)');
|
||||||
KMRenderMobileBindTexture(aTexture);
|
operationUnverified := False;
|
||||||
try
|
|
||||||
case aMode of
|
|
||||||
tfRGB5A1:
|
|
||||||
begin
|
|
||||||
CastleGLES.glTexParameteri(CastleGLES.GL_TEXTURE_2D, CastleGLES.GL_TEXTURE_SWIZZLE_R, CastleGLES.GL_RED);
|
|
||||||
CastleGLES.glTexParameteri(CastleGLES.GL_TEXTURE_2D, CastleGLES.GL_TEXTURE_SWIZZLE_G, CastleGLES.GL_GREEN);
|
|
||||||
CastleGLES.glTexParameteri(CastleGLES.GL_TEXTURE_2D, CastleGLES.GL_TEXTURE_SWIZZLE_B, CastleGLES.GL_BLUE);
|
|
||||||
CastleGLES.glTexParameteri(CastleGLES.GL_TEXTURE_2D, CastleGLES.GL_TEXTURE_SWIZZLE_A, CastleGLES.GL_ALPHA);
|
|
||||||
CastleGLES.glTexImage2D(CastleGLES.GL_TEXTURE_2D, 0, CastleGLES.GL_RGB5_A1, aWidth, aHeight, 0, CastleGLES.GL_RGBA, CastleGLES.GL_UNSIGNED_BYTE, aData);
|
|
||||||
end;
|
end;
|
||||||
tfRGBA8:
|
|
||||||
|
bindAttempted := True;
|
||||||
|
operationUnverified := True;
|
||||||
|
KMRenderMobileBindTexture(aTexture);
|
||||||
|
RequireTextureNoGLError('update', 'glBindTexture');
|
||||||
|
operationUnverified := False;
|
||||||
|
|
||||||
|
case aMode of
|
||||||
|
tfRGB5A1, tfRGBA8:
|
||||||
begin
|
begin
|
||||||
|
operationUnverified := True;
|
||||||
CastleGLES.glTexParameteri(CastleGLES.GL_TEXTURE_2D, CastleGLES.GL_TEXTURE_SWIZZLE_R, CastleGLES.GL_RED);
|
CastleGLES.glTexParameteri(CastleGLES.GL_TEXTURE_2D, CastleGLES.GL_TEXTURE_SWIZZLE_R, CastleGLES.GL_RED);
|
||||||
|
RequireTextureNoGLError('update', 'glTexParameteri(GL_TEXTURE_SWIZZLE_R)');
|
||||||
|
operationUnverified := False;
|
||||||
|
operationUnverified := True;
|
||||||
CastleGLES.glTexParameteri(CastleGLES.GL_TEXTURE_2D, CastleGLES.GL_TEXTURE_SWIZZLE_G, CastleGLES.GL_GREEN);
|
CastleGLES.glTexParameteri(CastleGLES.GL_TEXTURE_2D, CastleGLES.GL_TEXTURE_SWIZZLE_G, CastleGLES.GL_GREEN);
|
||||||
|
RequireTextureNoGLError('update', 'glTexParameteri(GL_TEXTURE_SWIZZLE_G)');
|
||||||
|
operationUnverified := False;
|
||||||
|
operationUnverified := True;
|
||||||
CastleGLES.glTexParameteri(CastleGLES.GL_TEXTURE_2D, CastleGLES.GL_TEXTURE_SWIZZLE_B, CastleGLES.GL_BLUE);
|
CastleGLES.glTexParameteri(CastleGLES.GL_TEXTURE_2D, CastleGLES.GL_TEXTURE_SWIZZLE_B, CastleGLES.GL_BLUE);
|
||||||
|
RequireTextureNoGLError('update', 'glTexParameteri(GL_TEXTURE_SWIZZLE_B)');
|
||||||
|
operationUnverified := False;
|
||||||
|
operationUnverified := True;
|
||||||
CastleGLES.glTexParameteri(CastleGLES.GL_TEXTURE_2D, CastleGLES.GL_TEXTURE_SWIZZLE_A, CastleGLES.GL_ALPHA);
|
CastleGLES.glTexParameteri(CastleGLES.GL_TEXTURE_2D, CastleGLES.GL_TEXTURE_SWIZZLE_A, CastleGLES.GL_ALPHA);
|
||||||
CastleGLES.glTexImage2D(CastleGLES.GL_TEXTURE_2D, 0, CastleGLES.GL_RGBA8, aWidth, aHeight, 0, CastleGLES.GL_RGBA, CastleGLES.GL_UNSIGNED_BYTE, aData);
|
RequireTextureNoGLError('update', 'glTexParameteri(GL_TEXTURE_SWIZZLE_A)');
|
||||||
|
operationUnverified := False;
|
||||||
|
if aMode = tfRGB5A1 then
|
||||||
|
begin
|
||||||
|
operationUnverified := True;
|
||||||
|
CastleGLES.glTexImage2D(CastleGLES.GL_TEXTURE_2D, 0, CastleGLES.GL_RGB5_A1,
|
||||||
|
aWidth, aHeight, 0, CastleGLES.GL_RGBA, CastleGLES.GL_UNSIGNED_BYTE, aData);
|
||||||
|
RequireTextureNoGLError('update', 'glTexImage2D');
|
||||||
|
operationUnverified := False;
|
||||||
|
end
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
operationUnverified := True;
|
||||||
|
CastleGLES.glTexImage2D(CastleGLES.GL_TEXTURE_2D, 0, CastleGLES.GL_RGBA8,
|
||||||
|
aWidth, aHeight, 0, CastleGLES.GL_RGBA, CastleGLES.GL_UNSIGNED_BYTE, aData);
|
||||||
|
RequireTextureNoGLError('update', 'glTexImage2D');
|
||||||
|
operationUnverified := False;
|
||||||
|
end;
|
||||||
end;
|
end;
|
||||||
tfAlpha8:
|
tfAlpha8:
|
||||||
begin
|
begin
|
||||||
|
operationUnverified := True;
|
||||||
CastleGLES.glTexParameteri(CastleGLES.GL_TEXTURE_2D, CastleGLES.GL_TEXTURE_SWIZZLE_R, CastleGLES.GL_ONE);
|
CastleGLES.glTexParameteri(CastleGLES.GL_TEXTURE_2D, CastleGLES.GL_TEXTURE_SWIZZLE_R, CastleGLES.GL_ONE);
|
||||||
|
RequireTextureNoGLError('update', 'glTexParameteri(GL_TEXTURE_SWIZZLE_R)');
|
||||||
|
operationUnverified := False;
|
||||||
|
operationUnverified := True;
|
||||||
CastleGLES.glTexParameteri(CastleGLES.GL_TEXTURE_2D, CastleGLES.GL_TEXTURE_SWIZZLE_G, CastleGLES.GL_ONE);
|
CastleGLES.glTexParameteri(CastleGLES.GL_TEXTURE_2D, CastleGLES.GL_TEXTURE_SWIZZLE_G, CastleGLES.GL_ONE);
|
||||||
|
RequireTextureNoGLError('update', 'glTexParameteri(GL_TEXTURE_SWIZZLE_G)');
|
||||||
|
operationUnverified := False;
|
||||||
|
operationUnverified := True;
|
||||||
CastleGLES.glTexParameteri(CastleGLES.GL_TEXTURE_2D, CastleGLES.GL_TEXTURE_SWIZZLE_B, CastleGLES.GL_ONE);
|
CastleGLES.glTexParameteri(CastleGLES.GL_TEXTURE_2D, CastleGLES.GL_TEXTURE_SWIZZLE_B, CastleGLES.GL_ONE);
|
||||||
|
RequireTextureNoGLError('update', 'glTexParameteri(GL_TEXTURE_SWIZZLE_B)');
|
||||||
|
operationUnverified := False;
|
||||||
|
operationUnverified := True;
|
||||||
CastleGLES.glTexParameteri(CastleGLES.GL_TEXTURE_2D, CastleGLES.GL_TEXTURE_SWIZZLE_A, CastleGLES.GL_RED);
|
CastleGLES.glTexParameteri(CastleGLES.GL_TEXTURE_2D, CastleGLES.GL_TEXTURE_SWIZZLE_A, CastleGLES.GL_RED);
|
||||||
CastleGLES.glTexImage2D(CastleGLES.GL_TEXTURE_2D, 0, CastleGLES.GL_R8, aWidth, aHeight, 0, CastleGLES.GL_RED, CastleGLES.GL_UNSIGNED_BYTE, @alphaData[0]);
|
RequireTextureNoGLError('update', 'glTexParameteri(GL_TEXTURE_SWIZZLE_A)');
|
||||||
|
operationUnverified := False;
|
||||||
|
operationUnverified := True;
|
||||||
|
CastleGLES.glTexImage2D(CastleGLES.GL_TEXTURE_2D, 0, CastleGLES.GL_R8,
|
||||||
|
aWidth, aHeight, 0, CastleGLES.GL_RED, CastleGLES.GL_UNSIGNED_BYTE, @alphaData[0]);
|
||||||
|
RequireTextureNoGLError('update', 'glTexImage2D');
|
||||||
|
operationUnverified := False;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
finally
|
except
|
||||||
KMRenderMobileBindTexture(0);
|
on E: Exception do
|
||||||
|
begin
|
||||||
|
KMRenderMobileInvalidateTextureBindingCache;
|
||||||
|
FinalizeUpdate(True);
|
||||||
|
if finalizeErrors <> '' then
|
||||||
|
E.Message := E.Message + '; secondary finalize failure(s): ' + finalizeErrors;
|
||||||
|
raise;
|
||||||
end;
|
end;
|
||||||
finally
|
end;
|
||||||
if restoreUnpackAlignment then
|
|
||||||
CastleGLES.glPixelStorei(CastleGLES.GL_UNPACK_ALIGNMENT, unpackAlignment);
|
FinalizeUpdate(False);
|
||||||
|
if finalizeErrors <> '' then
|
||||||
|
raise Exception.Create('texture update failed: ' + finalizeErrors);
|
||||||
|
except
|
||||||
|
KMRenderMobileInvalidateTextureBindingCache;
|
||||||
|
raise;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -314,25 +501,112 @@ end;
|
|||||||
function KMRenderMobileCreateTexture(aMinFilter, aMagFilter: TKMFilterType): Cardinal;
|
function KMRenderMobileCreateTexture(aMinFilter, aMagFilter: TKMFilterType): Cardinal;
|
||||||
var
|
var
|
||||||
texture: CastleGLES.GLuint;
|
texture: CastleGLES.GLuint;
|
||||||
|
cleanupErrors, cleanupEntryErrors, cleanupFailure: string;
|
||||||
|
cleanupReady, cleanupFailed: Boolean;
|
||||||
begin
|
begin
|
||||||
RequireTextureBackendReady;
|
RequireTextureBackendReady;
|
||||||
|
RequireNoPreexistingTextureGLError('create');
|
||||||
texture := 0;
|
texture := 0;
|
||||||
|
try
|
||||||
CastleGLES.glGenTextures(1, @texture);
|
CastleGLES.glGenTextures(1, @texture);
|
||||||
|
RequireTextureNoGLError('create', 'glGenTextures');
|
||||||
if texture = 0 then
|
if texture = 0 then
|
||||||
raise Exception.Create('Texture generation failed');
|
raise Exception.Create('Texture generation failed');
|
||||||
try
|
|
||||||
KMRenderMobileBindTexture(Cardinal(texture));
|
KMRenderMobileBindTexture(Cardinal(texture));
|
||||||
|
RequireTextureNoGLError('create', 'glBindTexture');
|
||||||
CastleGLES.glTexParameteri(CastleGLES.GL_TEXTURE_2D, CastleGLES.GL_TEXTURE_MIN_FILTER, TextureFilter(aMinFilter));
|
CastleGLES.glTexParameteri(CastleGLES.GL_TEXTURE_2D, CastleGLES.GL_TEXTURE_MIN_FILTER, TextureFilter(aMinFilter));
|
||||||
|
RequireTextureNoGLError('create', 'glTexParameteri(GL_TEXTURE_MIN_FILTER)');
|
||||||
CastleGLES.glTexParameteri(CastleGLES.GL_TEXTURE_2D, CastleGLES.GL_TEXTURE_MAG_FILTER, TextureFilter(aMagFilter));
|
CastleGLES.glTexParameteri(CastleGLES.GL_TEXTURE_2D, CastleGLES.GL_TEXTURE_MAG_FILTER, TextureFilter(aMagFilter));
|
||||||
|
RequireTextureNoGLError('create', 'glTexParameteri(GL_TEXTURE_MAG_FILTER)');
|
||||||
CastleGLES.glTexParameteri(CastleGLES.GL_TEXTURE_2D, CastleGLES.GL_TEXTURE_WRAP_S, CastleGLES.GL_CLAMP_TO_EDGE);
|
CastleGLES.glTexParameteri(CastleGLES.GL_TEXTURE_2D, CastleGLES.GL_TEXTURE_WRAP_S, CastleGLES.GL_CLAMP_TO_EDGE);
|
||||||
|
RequireTextureNoGLError('create', 'glTexParameteri(GL_TEXTURE_WRAP_S)');
|
||||||
CastleGLES.glTexParameteri(CastleGLES.GL_TEXTURE_2D, CastleGLES.GL_TEXTURE_WRAP_T, CastleGLES.GL_CLAMP_TO_EDGE);
|
CastleGLES.glTexParameteri(CastleGLES.GL_TEXTURE_2D, CastleGLES.GL_TEXTURE_WRAP_T, CastleGLES.GL_CLAMP_TO_EDGE);
|
||||||
|
RequireTextureNoGLError('create', 'glTexParameteri(GL_TEXTURE_WRAP_T)');
|
||||||
CastleGLES.glTexParameteri(CastleGLES.GL_TEXTURE_2D, CastleGLES.GL_TEXTURE_MAX_LEVEL, 0);
|
CastleGLES.glTexParameteri(CastleGLES.GL_TEXTURE_2D, CastleGLES.GL_TEXTURE_MAX_LEVEL, 0);
|
||||||
|
RequireTextureNoGLError('create', 'glTexParameteri(GL_TEXTURE_MAX_LEVEL)');
|
||||||
Result := Cardinal(texture);
|
Result := Cardinal(texture);
|
||||||
except
|
except
|
||||||
|
on E: Exception do
|
||||||
|
begin
|
||||||
|
KMRenderMobileInvalidateTextureBindingCache;
|
||||||
|
try
|
||||||
|
cleanupErrors := '';
|
||||||
|
cleanupEntryErrors := '';
|
||||||
|
cleanupFailure := '';
|
||||||
|
cleanupFailed := False;
|
||||||
|
if texture <> 0 then
|
||||||
|
begin
|
||||||
|
cleanupReady := False;
|
||||||
|
try
|
||||||
|
RequireTextureBackendReady;
|
||||||
|
cleanupReady := True;
|
||||||
|
except
|
||||||
|
on CleanupE: Exception do
|
||||||
|
begin
|
||||||
|
cleanupFailed := True;
|
||||||
|
cleanupFailure := 'cleanup unavailable: ' + CleanupE.Message;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
if cleanupReady then
|
||||||
|
begin
|
||||||
|
try
|
||||||
|
cleanupEntryErrors := DrainTextureGLErrors;
|
||||||
|
except
|
||||||
|
on CleanupE: Exception do
|
||||||
|
begin
|
||||||
|
cleanupFailed := True;
|
||||||
|
cleanupFailure := 'cleanup-entry drain failed: ' + CleanupE.Message;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
try
|
||||||
KMRenderMobileDeleteTexture(Cardinal(texture));
|
KMRenderMobileDeleteTexture(Cardinal(texture));
|
||||||
|
except
|
||||||
|
on CleanupE: Exception do
|
||||||
|
begin
|
||||||
|
cleanupFailed := True;
|
||||||
|
if cleanupFailure <> '' then cleanupFailure := cleanupFailure + '; ';
|
||||||
|
cleanupFailure := cleanupFailure + 'glDeleteTextures failed: ' + CleanupE.Message;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
try
|
||||||
|
cleanupErrors := DrainTextureGLErrors;
|
||||||
|
if cleanupErrors <> '' then
|
||||||
|
begin
|
||||||
|
cleanupFailed := True;
|
||||||
|
if cleanupFailure <> '' then cleanupFailure := cleanupFailure + '; ';
|
||||||
|
cleanupFailure := cleanupFailure + 'glDeleteTextures GLES error(s): ' + cleanupErrors;
|
||||||
|
end;
|
||||||
|
except
|
||||||
|
on CleanupE: Exception do
|
||||||
|
begin
|
||||||
|
cleanupFailed := True;
|
||||||
|
if cleanupFailure <> '' then cleanupFailure := cleanupFailure + '; ';
|
||||||
|
cleanupFailure := cleanupFailure + 'cleanup drain failed: ' + CleanupE.Message;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
if cleanupEntryErrors <> '' then
|
||||||
|
begin
|
||||||
|
if cleanupFailed then
|
||||||
|
begin
|
||||||
|
if cleanupFailure <> '' then cleanupFailure := cleanupFailure + '; ';
|
||||||
|
cleanupFailure := cleanupFailure + 'cleanup-entry GLES error(s): ' + cleanupEntryErrors;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
KMRenderMobileInvalidateTextureBindingCache;
|
||||||
|
if cleanupFailed then
|
||||||
|
try
|
||||||
|
E.Message := E.Message + '; texture create cleanup failed: ' + cleanupFailure;
|
||||||
|
except
|
||||||
|
end;
|
||||||
|
except
|
||||||
|
KMRenderMobileInvalidateTextureBindingCache;
|
||||||
|
end;
|
||||||
raise;
|
raise;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
procedure KMRenderMobileDeleteTexture(aTexId: Cardinal);
|
procedure KMRenderMobileDeleteTexture(aTexId: Cardinal);
|
||||||
|
|||||||
@@ -29,7 +29,8 @@
|
|||||||
"src/render/KM_RenderPool.pas",
|
"src/render/KM_RenderPool.pas",
|
||||||
"src/gui/KM_InterfaceGame.pas",
|
"src/gui/KM_InterfaceGame.pas",
|
||||||
"src/render/KM_RenderTerrain.pas",
|
"src/render/KM_RenderTerrain.pas",
|
||||||
"src/res/KM_ResInterpolation.pas"
|
"src/res/KM_ResInterpolation.pas",
|
||||||
|
"src/res/KM_ResFonts.pas"
|
||||||
],
|
],
|
||||||
"source_paths": [
|
"source_paths": [
|
||||||
"code/resource"
|
"code/resource"
|
||||||
|
|||||||
@@ -9,6 +9,14 @@
|
|||||||
{"kind": "file", "path": "data/defines/tiles.json"},
|
{"kind": "file", "path": "data/defines/tiles.json"},
|
||||||
{"kind": "file", "path": "data/gfx/pal0.bbm"},
|
{"kind": "file", "path": "data/gfx/pal0.bbm"},
|
||||||
{"kind": "file", "path": "data/defines/mapelem.dat"},
|
{"kind": "file", "path": "data/defines/mapelem.dat"},
|
||||||
{"kind": "file", "path": "data/locales.txt"}
|
{"kind": "file", "path": "data/locales.txt"},
|
||||||
|
{"kind": "file", "path": "data/gfx/fonts/antiqua.fntx"},
|
||||||
|
{"kind": "file", "path": "data/gfx/fonts/game.fntx"},
|
||||||
|
{"kind": "file", "path": "data/gfx/fonts/grey.fntx"},
|
||||||
|
{"kind": "file", "path": "data/gfx/fonts/metal.fntx"},
|
||||||
|
{"kind": "file", "path": "data/gfx/fonts/mini.fntx"},
|
||||||
|
{"kind": "file", "path": "data/gfx/fonts/outline.fntx"},
|
||||||
|
{"kind": "file", "path": "data/gfx/fonts/arial.fntx"},
|
||||||
|
{"kind": "file", "path": "data/gfx/fonts/consolas8.fntx"}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -190,6 +190,8 @@ uses
|
|||||||
// Creating everything needed for MainMenu, game stuff is created on StartGame
|
// Creating everything needed for MainMenu, game stuff is created on StartGame
|
||||||
constructor TKMGameApp.Create(aScreenX, aScreenY: Word; aVSync: Boolean; aOnLoadingStep: TKMEvent;
|
constructor TKMGameApp.Create(aScreenX, aScreenY: Word; aVSync: Boolean; aOnLoadingStep: TKMEvent;
|
||||||
aOnLoadingText: TUnicodeStringEvent; aOnStatusBarUpdate: TProc<TKMStatusBarPanel, string>; NoMusic: Boolean = False);
|
aOnLoadingText: TUnicodeStringEvent; aOnStatusBarUpdate: TProc<TKMStatusBarPanel, string>; NoMusic: Boolean = False);
|
||||||
|
var
|
||||||
|
candidateGeneration: QWord;
|
||||||
begin
|
begin
|
||||||
inherited Create;
|
inherited Create;
|
||||||
|
|
||||||
@@ -225,6 +227,24 @@ begin
|
|||||||
gRes := TKMResource.Create(aOnLoadingStep, aOnLoadingText);
|
gRes := TKMResource.Create(aOnLoadingStep, aOnLoadingText);
|
||||||
gRes.LoadMainResources(gGameSettings.Locale, gGameSettings.GFX.LoadFullFonts);
|
gRes.LoadMainResources(gGameSettings.Locale, gGameSettings.GFX.LoadFullFonts);
|
||||||
|
|
||||||
|
candidateGeneration := fGraphicsAttemptGeneration;
|
||||||
|
if candidateGeneration <> 0 then
|
||||||
|
begin
|
||||||
|
if IsCurrentGraphicsAttempt(candidateGeneration)
|
||||||
|
and TKMRender.TextureBackendReady
|
||||||
|
and (gRes <> nil)
|
||||||
|
and (gRes.Fonts <> nil) then
|
||||||
|
begin
|
||||||
|
gRes.EnsureFontResourcesForGeneration(candidateGeneration, fGraphicsAttemptGeneration);
|
||||||
|
|
||||||
|
if IsCurrentGraphicsAttempt(candidateGeneration)
|
||||||
|
and TKMRender.TextureBackendReady then
|
||||||
|
ReevaluateApplicationGraphicsReadiness(candidateGeneration);
|
||||||
|
end
|
||||||
|
else if IsCurrentGraphicsAttempt(candidateGeneration) then
|
||||||
|
ReevaluateApplicationGraphicsReadiness(candidateGeneration);
|
||||||
|
end;
|
||||||
|
|
||||||
{$IFDEF USE_MAD_EXCEPT}gExceptions.LoadTranslation;{$ENDIF}
|
{$IFDEF USE_MAD_EXCEPT}gExceptions.LoadTranslation;{$ENDIF}
|
||||||
|
|
||||||
//Show the message if user has old OpenGL drivers (pre-1.4)
|
//Show the message if user has old OpenGL drivers (pre-1.4)
|
||||||
@@ -286,9 +306,24 @@ begin
|
|||||||
if not TKMRender.TextureBackendReady then
|
if not TKMRender.TextureBackendReady then
|
||||||
raise Exception.Create('Graphics texture backend is not ready after context restore');
|
raise Exception.Create('Graphics texture backend is not ready after context restore');
|
||||||
|
|
||||||
|
if gRes <> nil then
|
||||||
|
gRes.InvalidateFontGraphicsContext;
|
||||||
|
|
||||||
if (gRes <> nil) and (gRes.DataState <> rlsNone) then
|
if (gRes <> nil) and (gRes.DataState <> rlsNone) then
|
||||||
gRes.StartGameSpriteRecovery;
|
gRes.StartGameSpriteRecovery;
|
||||||
|
|
||||||
|
if IsCurrentGraphicsAttempt(candidateGeneration)
|
||||||
|
and TKMRender.TextureBackendReady
|
||||||
|
and (gRes <> nil)
|
||||||
|
and (gRes.Fonts <> nil) then
|
||||||
|
begin
|
||||||
|
gRes.EnsureFontResourcesForGeneration(candidateGeneration, fGraphicsAttemptGeneration);
|
||||||
|
|
||||||
|
if IsCurrentGraphicsAttempt(candidateGeneration)
|
||||||
|
and TKMRender.TextureBackendReady then
|
||||||
|
ReevaluateApplicationGraphicsReadiness(candidateGeneration);
|
||||||
|
end
|
||||||
|
else if IsCurrentGraphicsAttempt(candidateGeneration) then
|
||||||
ReevaluateApplicationGraphicsReadiness(candidateGeneration);
|
ReevaluateApplicationGraphicsReadiness(candidateGeneration);
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
@@ -297,6 +332,13 @@ begin
|
|||||||
fGraphicsAttemptGeneration := 0;
|
fGraphicsAttemptGeneration := 0;
|
||||||
fApplicationGraphicsReadyGeneration := 0;
|
fApplicationGraphicsReadyGeneration := 0;
|
||||||
|
|
||||||
|
if gRes <> nil then
|
||||||
|
try
|
||||||
|
gRes.InvalidateFontGraphicsContext;
|
||||||
|
finally
|
||||||
|
TKMRender.GraphicsContextChanged(False);
|
||||||
|
end
|
||||||
|
else
|
||||||
TKMRender.GraphicsContextChanged(False);
|
TKMRender.GraphicsContextChanged(False);
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
@@ -312,6 +354,10 @@ begin
|
|||||||
Exit;
|
Exit;
|
||||||
if not TKMRender.SampledProgramReady then
|
if not TKMRender.SampledProgramReady then
|
||||||
Exit;
|
Exit;
|
||||||
|
if gRes = nil then
|
||||||
|
Exit;
|
||||||
|
if not gRes.FontResourcesReadyForGeneration(aCandidateGeneration) then
|
||||||
|
Exit;
|
||||||
if gGame <> nil then
|
if gGame <> nil then
|
||||||
begin
|
begin
|
||||||
if gRes = nil then
|
if gRes = nil then
|
||||||
@@ -330,6 +376,15 @@ begin
|
|||||||
and (fGraphicsGeneration <> 0)
|
and (fGraphicsGeneration <> 0)
|
||||||
and (fGraphicsAttemptGeneration = fGraphicsGeneration)
|
and (fGraphicsAttemptGeneration = fGraphicsGeneration)
|
||||||
and (fApplicationGraphicsReadyGeneration = fGraphicsGeneration);
|
and (fApplicationGraphicsReadyGeneration = fGraphicsGeneration);
|
||||||
|
if not Result then
|
||||||
|
Exit;
|
||||||
|
if gRes = nil then
|
||||||
|
begin
|
||||||
|
Result := False;
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
|
||||||
|
Result := gRes.FontResourcesReadyForGeneration(fGraphicsGeneration);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,966 @@
|
|||||||
|
unit KM_ResFonts;
|
||||||
|
{$I KaM_Remake.inc}
|
||||||
|
interface
|
||||||
|
uses
|
||||||
|
SysUtils,
|
||||||
|
KM_CommonTypes, KM_RenderTypes,
|
||||||
|
KM_Points;
|
||||||
|
|
||||||
|
|
||||||
|
const
|
||||||
|
FONT_TAB_WIDTH = 30;
|
||||||
|
|
||||||
|
type
|
||||||
|
//* Font type
|
||||||
|
TKMFont = (
|
||||||
|
fntAntiqua,
|
||||||
|
fntGame,
|
||||||
|
fntGrey,
|
||||||
|
fntMetal,
|
||||||
|
fntMini,
|
||||||
|
fntOutline,
|
||||||
|
fntArial, // Arial for MP chat
|
||||||
|
fntMonospaced // Debug overlay font (Consolas 8. Set WordSpacing to any Char.Width)
|
||||||
|
);
|
||||||
|
|
||||||
|
const
|
||||||
|
FontTypeName: array [TKMFont] of string = (
|
||||||
|
// Used in:
|
||||||
|
// - data\gfx\fonts\filenames
|
||||||
|
// - utils\FontX Generator\fonts.xml
|
||||||
|
|
||||||
|
'antiqua',
|
||||||
|
'game',
|
||||||
|
'grey',
|
||||||
|
'metal',
|
||||||
|
'mini',
|
||||||
|
'outline',
|
||||||
|
'arial',
|
||||||
|
'consolas8'
|
||||||
|
);
|
||||||
|
|
||||||
|
type
|
||||||
|
TKMFontLoadLevel = (fllFull, fllMinimal);
|
||||||
|
{
|
||||||
|
Removed fonts that were in KaM:
|
||||||
|
Adam (unused)
|
||||||
|
Briefing (same typeface as Antiqua, just darker)
|
||||||
|
Font01 (damaged)
|
||||||
|
KMLobby (used for internet lobby in TPR)
|
||||||
|
MainA (identical to MainMapGold in all game versions)
|
||||||
|
MainA.old (probably never meant to be included in the release anyway)
|
||||||
|
MainB (identical to Game)
|
||||||
|
MainMapGold (same typeface as Metal, just with a goldish tint)
|
||||||
|
Minimum (same as mini but with less characters)
|
||||||
|
System (unused)
|
||||||
|
Won (same typeface as Metal, just with a blueish tint)
|
||||||
|
}
|
||||||
|
|
||||||
|
TKMFontInfo = record
|
||||||
|
public
|
||||||
|
TexMode: TKMTexFormat; //Format font texture needs to be in
|
||||||
|
end;
|
||||||
|
|
||||||
|
TKMLetter = packed record
|
||||||
|
public
|
||||||
|
Width, Height: Word;
|
||||||
|
YOffset: SmallInt;
|
||||||
|
AtlasId: Word; //Was Unknown field, we use it for multi-atlas fonts to mark the letters location
|
||||||
|
u1, v1, u2, v2: Single; //Location within texture atlas
|
||||||
|
end;
|
||||||
|
|
||||||
|
TKMFontAtlas = record
|
||||||
|
TexID: Cardinal;
|
||||||
|
GpuCurrent: Boolean;
|
||||||
|
TexData: TKMCardinalArray;
|
||||||
|
end;
|
||||||
|
TKMFontAtlasArray = array of TKMFontAtlas;
|
||||||
|
|
||||||
|
TKMFontSpec = class
|
||||||
|
const
|
||||||
|
DEFAULT_EXT = 'fntx';
|
||||||
|
FNTX_HEAD: AnsiString = 'FNTX';
|
||||||
|
FONTS_FOLDER = 'data' + PathDelim + 'gfx' + PathDelim + 'fonts' + PathDelim;
|
||||||
|
private
|
||||||
|
fFont: TKMFont;
|
||||||
|
procedure LoadFontX(const aFileName: string; aLoadLevel: TKMFontLoadLevel;
|
||||||
|
aAllowReuseAtlas0: Boolean; aPriorAtlas0ID: Cardinal); overload;
|
||||||
|
function GetTexID(aIndex: Integer): Cardinal;
|
||||||
|
function GetLineHeight: SmallInt;
|
||||||
|
protected
|
||||||
|
fTexSizeX, fTexSizeY: Word; //All atlases have same dimensions
|
||||||
|
//Character atlases
|
||||||
|
fAtlasCount: Byte;
|
||||||
|
fAtlases: TKMFontAtlasArray;
|
||||||
|
fCharCount: Word;
|
||||||
|
fBaseHeight, fWordSpacing, fCharSpacing: SmallInt;
|
||||||
|
fLineSpacing: Byte; //Not in KaM files, we use custom value that fits well
|
||||||
|
fCodepage: Word;
|
||||||
|
fIsUnicode: Boolean;
|
||||||
|
|
||||||
|
fMaxWidth: Integer;
|
||||||
|
public
|
||||||
|
Used: array [0..High(Word)] of Byte;
|
||||||
|
Letters: array [0..High(Word)] of TKMLetter;
|
||||||
|
|
||||||
|
constructor Create(aFont: TKMFont);
|
||||||
|
|
||||||
|
procedure LoadFontX(const aFileName: string; aLoadLevel: TKMFontLoadLevel = fllFull); overload;
|
||||||
|
procedure GenerateTextures(aTexMode: TKMTexFormat);
|
||||||
|
procedure Compact;
|
||||||
|
|
||||||
|
function GetLetter(aChar: WideChar): TKMLetter;
|
||||||
|
property AtlasCount: Byte read fAtlasCount;
|
||||||
|
property TexID[aIndex: Integer]: Cardinal read GetTexID;
|
||||||
|
|
||||||
|
property CharCount: Word read fCharCount;
|
||||||
|
property CharSpacing: SmallInt read fCharSpacing;
|
||||||
|
property LineSpacing: Byte read fLineSpacing;
|
||||||
|
property LineHeight: SmallInt read GetLineHeight;
|
||||||
|
property BaseHeight: SmallInt read fBaseHeight;
|
||||||
|
property WordSpacing: SmallInt read fWordSpacing;
|
||||||
|
|
||||||
|
function GetCharWidth(aChar: WideChar; aConsiderEolSymbol: Boolean = False): Integer;
|
||||||
|
function WordWrap(aText: UnicodeString; aMaxPxWidth: Integer; aForced: Boolean; aIndentAfterNL: Boolean;
|
||||||
|
aTabWidth: Integer = FONT_TAB_WIDTH): UnicodeString;
|
||||||
|
function CharsThatFit(const aText: UnicodeString; aMaxPxWidth: Integer; aRound: Boolean = False;
|
||||||
|
aConsiderEolSymbol: Boolean = False; aTabWidth: Integer = FONT_TAB_WIDTH): Integer;
|
||||||
|
function GetTextSize(const aText: UnicodeString; var aLineCount: Integer; aCountMarkup: Boolean = False;
|
||||||
|
aConsiderEolSymbol: Boolean = False; aTabWidth: Integer = FONT_TAB_WIDTH): TKMPoint; overload;
|
||||||
|
function GetTextSize(const aText: UnicodeString; aCountMarkup: Boolean = False; aConsiderEolSymbol: Boolean = False;
|
||||||
|
aTabWidth: Integer = FONT_TAB_WIDTH): TKMPoint; overload;
|
||||||
|
function GetMaxPrintWidthOfStrings(aStrings: array of string): Integer;
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
//Collection of fonts
|
||||||
|
TKMResFonts = class
|
||||||
|
private
|
||||||
|
fContentSetValid: Boolean;
|
||||||
|
fLoadLevel: TKMFontLoadLevel;
|
||||||
|
fFontData: array [TKMFont] of TKMFontSpec;
|
||||||
|
function GetFontData(aIndex: TKMFont): TKMFontSpec;
|
||||||
|
public
|
||||||
|
constructor Create;
|
||||||
|
destructor Destroy; override;
|
||||||
|
|
||||||
|
property FontData[aIndex: TKMFont]: TKMFontSpec read GetFontData; default;
|
||||||
|
property LoadLevel: TKMFontLoadLevel read fLoadLevel;
|
||||||
|
function HasContentLevel(aRequiredLevel: TKMFontLoadLevel): Boolean;
|
||||||
|
function HasGPUAtlasLevel(aRequiredLevel: TKMFontLoadLevel): Boolean;
|
||||||
|
procedure InvalidateGPUAtlasContext;
|
||||||
|
procedure LoadFonts(aLoadLevel: TKMFontLoadLevel = fllFull);
|
||||||
|
function EnsureGPUAtlasLevel(aRequiredLevel: TKMFontLoadLevel): Boolean;
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
const
|
||||||
|
PLACEHOLDER_CHAR = 0; // Box, used for characters missing from font
|
||||||
|
|
||||||
|
FONT_INFO: array [TKMFont] of TKMFontInfo = (
|
||||||
|
(TexMode: tfRGB5A1),
|
||||||
|
(TexMode: tfAlpha8),
|
||||||
|
(TexMode: tfRGB5A1),
|
||||||
|
(TexMode: tfRGB5A1),
|
||||||
|
(TexMode: tfAlpha8),
|
||||||
|
(TexMode: tfRGB5A1),
|
||||||
|
(TexMode: tfRGBA8),
|
||||||
|
(TexMode: tfRGBA8)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
implementation
|
||||||
|
uses
|
||||||
|
Classes, Math,
|
||||||
|
{$IFDEF FPC} zstream, {$ENDIF}
|
||||||
|
{$IFDEF WDC} ZLib, {$ENDIF}
|
||||||
|
KM_Render, KaMResourceInput;
|
||||||
|
|
||||||
|
|
||||||
|
{ TKMFontSpec }
|
||||||
|
constructor TKMFontSpec.Create(aFont: TKMFont);
|
||||||
|
begin
|
||||||
|
inherited Create;
|
||||||
|
|
||||||
|
fFont := aFont;
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
procedure TKMFontSpec.LoadFontX(const aFileName: string; aLoadLevel: TKMFontLoadLevel = fllFull);
|
||||||
|
begin
|
||||||
|
LoadFontX(aFileName, aLoadLevel, False, 0);
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
procedure TKMFontSpec.LoadFontX(const aFileName: string; aLoadLevel: TKMFontLoadLevel;
|
||||||
|
aAllowReuseAtlas0: Boolean; aPriorAtlas0ID: Cardinal);
|
||||||
|
var
|
||||||
|
inputStream: TStream;
|
||||||
|
decompressionStream: TDecompressionStream;
|
||||||
|
head: AnsiString;
|
||||||
|
sourceUsed, resultUsed: array of Byte;
|
||||||
|
sourceLetters: array of TKMLetter;
|
||||||
|
stagedAtlases: TKMFontAtlasArray;
|
||||||
|
sourceAtlasCount, loadCount: Byte;
|
||||||
|
texWidth, texHeight: Word;
|
||||||
|
stagedBaseHeight, stagedWordSpacing, stagedCharSpacing: SmallInt;
|
||||||
|
stagedLineSpacing: Byte;
|
||||||
|
pixelCount, byteCount, remaining: UInt64;
|
||||||
|
readCount, I: Integer;
|
||||||
|
extraByte: Byte;
|
||||||
|
discardBuffer: array[0..65535] of Byte;
|
||||||
|
u1, v1, u2, v2: Single;
|
||||||
|
oldIndex: Integer;
|
||||||
|
reuseAtlas0: Boolean;
|
||||||
|
begin
|
||||||
|
inputStream := KaMOpenResourceStream(aFileName);
|
||||||
|
if inputStream = nil then
|
||||||
|
raise Exception.Create('Font resource returned no stream');
|
||||||
|
try
|
||||||
|
decompressionStream := TDecompressionStream.Create(inputStream);
|
||||||
|
try
|
||||||
|
SetLength(sourceUsed, 65536);
|
||||||
|
SetLength(resultUsed, 65536);
|
||||||
|
SetLength(sourceLetters, 65536);
|
||||||
|
SetLength(head, 4);
|
||||||
|
decompressionStream.ReadBuffer(head[1], 4);
|
||||||
|
if head <> FNTX_HEAD then
|
||||||
|
raise Exception.Create('Invalid FNTX header');
|
||||||
|
|
||||||
|
decompressionStream.ReadBuffer(stagedBaseHeight, SizeOf(stagedBaseHeight));
|
||||||
|
decompressionStream.ReadBuffer(stagedWordSpacing, SizeOf(stagedWordSpacing));
|
||||||
|
decompressionStream.ReadBuffer(stagedCharSpacing, SizeOf(stagedCharSpacing));
|
||||||
|
decompressionStream.ReadBuffer(stagedLineSpacing, SizeOf(stagedLineSpacing));
|
||||||
|
decompressionStream.ReadBuffer(sourceUsed[0], Length(sourceUsed));
|
||||||
|
for I := 0 to High(Word) do
|
||||||
|
if sourceUsed[I] <> 0 then
|
||||||
|
decompressionStream.ReadBuffer(sourceLetters[I], SizeOf(TKMLetter));
|
||||||
|
|
||||||
|
decompressionStream.ReadBuffer(sourceAtlasCount, SizeOf(sourceAtlasCount));
|
||||||
|
decompressionStream.ReadBuffer(texWidth, SizeOf(texWidth));
|
||||||
|
decompressionStream.ReadBuffer(texHeight, SizeOf(texHeight));
|
||||||
|
if sourceAtlasCount = 0 then
|
||||||
|
raise Exception.Create('FNTX contains no atlases');
|
||||||
|
if (texWidth = 0) or (texHeight = 0) then
|
||||||
|
raise Exception.Create('FNTX has invalid atlas dimensions');
|
||||||
|
pixelCount := UInt64(texWidth) * UInt64(texHeight);
|
||||||
|
byteCount := pixelCount * SizeOf(Cardinal);
|
||||||
|
if (pixelCount > UInt64(High(SizeInt))) or (byteCount > UInt64(High(LongInt))) then
|
||||||
|
raise Exception.Create('FNTX atlas dimensions exceed supported size');
|
||||||
|
|
||||||
|
for I := 0 to High(Word) do
|
||||||
|
if sourceUsed[I] <> 0 then
|
||||||
|
begin
|
||||||
|
if sourceLetters[I].AtlasId >= sourceAtlasCount then
|
||||||
|
raise Exception.Create('FNTX glyph references an invalid atlas');
|
||||||
|
u1 := sourceLetters[I].u1;
|
||||||
|
v1 := sourceLetters[I].v1;
|
||||||
|
u2 := sourceLetters[I].u2;
|
||||||
|
v2 := sourceLetters[I].v2;
|
||||||
|
if IsNan(u1) or IsInfinite(u1) or IsNan(v1) or IsInfinite(v1)
|
||||||
|
or IsNan(u2) or IsInfinite(u2) or IsNan(v2) or IsInfinite(v2)
|
||||||
|
or (u1 < 0) or (u1 > 1) or (v1 < 0) or (v1 > 1)
|
||||||
|
or (u2 < 0) or (u2 > 1) or (v2 < 0) or (v2 > 1)
|
||||||
|
or (u1 > u2) or (v1 > v2) then
|
||||||
|
raise Exception.Create('FNTX glyph has invalid atlas coordinates');
|
||||||
|
end;
|
||||||
|
|
||||||
|
if aLoadLevel = fllMinimal then
|
||||||
|
loadCount := 1
|
||||||
|
else
|
||||||
|
loadCount := sourceAtlasCount;
|
||||||
|
if (sourceUsed[PLACEHOLDER_CHAR] <> 0)
|
||||||
|
and (sourceLetters[PLACEHOLDER_CHAR].AtlasId >= loadCount) then
|
||||||
|
raise Exception.Create('FNTX fallback glyph is absent from selected atlas level');
|
||||||
|
for I := 0 to High(Word) do
|
||||||
|
if (sourceUsed[I] <> 0)
|
||||||
|
and ((aLoadLevel = fllFull) or (sourceLetters[I].AtlasId = 0)) then
|
||||||
|
resultUsed[I] := sourceUsed[I];
|
||||||
|
|
||||||
|
SetLength(stagedAtlases, loadCount);
|
||||||
|
for I := 0 to loadCount - 1 do
|
||||||
|
begin
|
||||||
|
stagedAtlases[I].TexID := 0;
|
||||||
|
stagedAtlases[I].GpuCurrent := False;
|
||||||
|
SetLength(stagedAtlases[I].TexData, SizeInt(pixelCount));
|
||||||
|
end;
|
||||||
|
for I := 0 to sourceAtlasCount - 1 do
|
||||||
|
if I < loadCount then
|
||||||
|
decompressionStream.ReadBuffer(stagedAtlases[I].TexData[0], LongInt(byteCount))
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
remaining := byteCount;
|
||||||
|
while remaining > 0 do
|
||||||
|
begin
|
||||||
|
if remaining > SizeOf(discardBuffer) then
|
||||||
|
readCount := SizeOf(discardBuffer)
|
||||||
|
else
|
||||||
|
readCount := Integer(remaining);
|
||||||
|
decompressionStream.ReadBuffer(discardBuffer[0], readCount);
|
||||||
|
Dec(remaining, UInt64(readCount));
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
if decompressionStream.Read(extraByte, 1) <> 0 then
|
||||||
|
raise Exception.Create('FNTX contains trailing decoded data');
|
||||||
|
finally
|
||||||
|
decompressionStream.Free;
|
||||||
|
end;
|
||||||
|
finally
|
||||||
|
inputStream.Free;
|
||||||
|
end;
|
||||||
|
|
||||||
|
reuseAtlas0 := aAllowReuseAtlas0 and (aPriorAtlas0ID <> 0)
|
||||||
|
and (Length(fAtlases) > 0) and (fAtlases[0].TexID = aPriorAtlas0ID)
|
||||||
|
and fAtlases[0].GpuCurrent and TKMRender.TextureBackendReady;
|
||||||
|
for oldIndex := 0 to High(fAtlases) do
|
||||||
|
begin
|
||||||
|
if (oldIndex = 0) and reuseAtlas0 then
|
||||||
|
Continue;
|
||||||
|
if fAtlases[oldIndex].GpuCurrent and (fAtlases[oldIndex].TexID <> 0)
|
||||||
|
and TKMRender.TextureBackendReady then
|
||||||
|
TKMRender.DeleteTexture(fAtlases[oldIndex].TexID);
|
||||||
|
fAtlases[oldIndex].TexID := 0;
|
||||||
|
fAtlases[oldIndex].GpuCurrent := False;
|
||||||
|
end;
|
||||||
|
if reuseAtlas0 and (Length(stagedAtlases) > 0) then
|
||||||
|
begin
|
||||||
|
stagedAtlases[0].TexID := aPriorAtlas0ID;
|
||||||
|
stagedAtlases[0].GpuCurrent := True;
|
||||||
|
end;
|
||||||
|
|
||||||
|
for I := 0 to High(Word) do
|
||||||
|
begin
|
||||||
|
Used[I] := resultUsed[I];
|
||||||
|
Letters[I] := sourceLetters[I];
|
||||||
|
end;
|
||||||
|
fBaseHeight := stagedBaseHeight;
|
||||||
|
fWordSpacing := stagedWordSpacing;
|
||||||
|
fCharSpacing := stagedCharSpacing;
|
||||||
|
fLineSpacing := stagedLineSpacing;
|
||||||
|
fCodepage := 0;
|
||||||
|
fIsUnicode := True;
|
||||||
|
fCharCount := 65535;
|
||||||
|
fAtlasCount := loadCount;
|
||||||
|
fTexSizeX := texWidth;
|
||||||
|
fTexSizeY := texHeight;
|
||||||
|
fAtlases := stagedAtlases;
|
||||||
|
stagedAtlases := nil;
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
// After font has been loaded and texture generated we can flush temp data
|
||||||
|
procedure TKMFontSpec.Compact;
|
||||||
|
var
|
||||||
|
I: Integer;
|
||||||
|
begin
|
||||||
|
//Discard texture data to save mem
|
||||||
|
for I := 0 to fAtlasCount - 1 do
|
||||||
|
SetLength(fAtlases[I].TexData, 0);
|
||||||
|
|
||||||
|
fTexSizeX := 0;
|
||||||
|
fTexSizeY := 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
// Generate color texture from prepared data
|
||||||
|
procedure TKMFontSpec.GenerateTextures(aTexMode: TKMTexFormat);
|
||||||
|
var
|
||||||
|
I: Integer;
|
||||||
|
pixelCount: UInt64;
|
||||||
|
textureID: Cardinal;
|
||||||
|
begin
|
||||||
|
if not TKMRender.TextureBackendReady then
|
||||||
|
raise Exception.Create('Cannot upload font atlases without a ready texture backend');
|
||||||
|
|
||||||
|
for I := 0 to fAtlasCount - 1 do
|
||||||
|
begin
|
||||||
|
if fAtlases[I].GpuCurrent and (fAtlases[I].TexID <> 0) then
|
||||||
|
Continue;
|
||||||
|
|
||||||
|
if not TKMRender.TextureBackendReady then
|
||||||
|
raise Exception.Create('Texture backend became unavailable during font atlas upload');
|
||||||
|
fAtlases[I].GpuCurrent := False;
|
||||||
|
fAtlases[I].TexID := 0;
|
||||||
|
pixelCount := UInt64(fTexSizeX) * UInt64(fTexSizeY);
|
||||||
|
if (fTexSizeX = 0) or (fTexSizeY = 0)
|
||||||
|
or (pixelCount = 0) or (pixelCount > UInt64(High(SizeInt)))
|
||||||
|
or (pixelCount * SizeOf(Cardinal) > UInt64(High(LongInt)))
|
||||||
|
or (UInt64(Length(fAtlases[I].TexData)) <> pixelCount) then
|
||||||
|
raise Exception.Create('Font atlas pixels are unavailable or have invalid dimensions');
|
||||||
|
|
||||||
|
textureID := TKMRender.GenTexture(fTexSizeX, fTexSizeY, @fAtlases[I].TexData[0], aTexMode, ftNearest, ftNearest);
|
||||||
|
if textureID = 0 then
|
||||||
|
raise Exception.Create('Font atlas texture upload returned no texture ID');
|
||||||
|
if not TKMRender.TextureBackendReady then
|
||||||
|
raise Exception.Create('Texture backend became unavailable during font atlas upload');
|
||||||
|
|
||||||
|
fAtlases[I].TexID := textureID;
|
||||||
|
fAtlases[I].GpuCurrent := True;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
function TKMFontSpec.GetLetter(aChar: WideChar): TKMLetter;
|
||||||
|
begin
|
||||||
|
if Used[Ord(aChar)] <> 0 then
|
||||||
|
Result := Letters[Ord(aChar)]
|
||||||
|
else
|
||||||
|
Result := Letters[PLACEHOLDER_CHAR];
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
function TKMFontSpec.GetTexID(aIndex: Integer): Cardinal;
|
||||||
|
begin
|
||||||
|
Result := fAtlases[aIndex].TexID;
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
{ TKMResFonts }
|
||||||
|
constructor TKMResFonts.Create;
|
||||||
|
var
|
||||||
|
F: TKMFont;
|
||||||
|
begin
|
||||||
|
inherited;
|
||||||
|
|
||||||
|
fContentSetValid := False;
|
||||||
|
fLoadLevel := fllMinimal;
|
||||||
|
|
||||||
|
for F := Low(TKMFont) to High(TKMFont) do
|
||||||
|
fFontData[F] := TKMFontSpec.Create(F);
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
destructor TKMResFonts.Destroy;
|
||||||
|
var
|
||||||
|
F: TKMFont;
|
||||||
|
I: Integer;
|
||||||
|
firstDeleteError: string;
|
||||||
|
deleteFailed: Boolean;
|
||||||
|
begin
|
||||||
|
firstDeleteError := '';
|
||||||
|
deleteFailed := False;
|
||||||
|
try
|
||||||
|
for F := Low(TKMFont) to High(TKMFont) do
|
||||||
|
if fFontData[F] <> nil then
|
||||||
|
for I := 0 to High(fFontData[F].fAtlases) do
|
||||||
|
if (fFontData[F].fAtlases[I].TexID <> 0)
|
||||||
|
and fFontData[F].fAtlases[I].GpuCurrent then
|
||||||
|
begin
|
||||||
|
if TKMRender.TextureBackendReady then
|
||||||
|
try
|
||||||
|
TKMRender.DeleteTexture(fFontData[F].fAtlases[I].TexID);
|
||||||
|
except
|
||||||
|
on E: Exception do
|
||||||
|
if not deleteFailed then
|
||||||
|
begin
|
||||||
|
firstDeleteError := E.Message;
|
||||||
|
deleteFailed := True;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
fFontData[F].fAtlases[I].TexID := 0;
|
||||||
|
fFontData[F].fAtlases[I].GpuCurrent := False;
|
||||||
|
end;
|
||||||
|
finally
|
||||||
|
for F := Low(TKMFont) to High(TKMFont) do
|
||||||
|
fFontData[F].Free;
|
||||||
|
end;
|
||||||
|
|
||||||
|
inherited;
|
||||||
|
if deleteFailed then
|
||||||
|
raise Exception.Create(firstDeleteError);
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
function TKMResFonts.GetFontData(aIndex: TKMFont): TKMFontSpec;
|
||||||
|
begin
|
||||||
|
Result := fFontData[aIndex];
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
procedure TKMResFonts.LoadFonts(aLoadLevel: TKMFontLoadLevel = fllFull);
|
||||||
|
type
|
||||||
|
TReuseCandidate = record
|
||||||
|
CanReuse: Boolean;
|
||||||
|
PriorAtlas0ID: Cardinal;
|
||||||
|
end;
|
||||||
|
TNewFontAtlasID = record
|
||||||
|
Font: TKMFont;
|
||||||
|
AtlasIndex: Integer;
|
||||||
|
TexID: Cardinal;
|
||||||
|
end;
|
||||||
|
var
|
||||||
|
F: TKMFont;
|
||||||
|
I, decodedCount, atlasCount: Integer;
|
||||||
|
relativeIdentity, fntRef: string;
|
||||||
|
reuseCandidate: array[TKMFont] of TReuseCandidate;
|
||||||
|
beforeID: array of Cardinal;
|
||||||
|
newIDs: array of TNewFontAtlasID;
|
||||||
|
newID: TNewFontAtlasID;
|
||||||
|
seen: Boolean;
|
||||||
|
|
||||||
|
procedure RecordCreatedIDs(aFont: TKMFont);
|
||||||
|
var
|
||||||
|
J, K: Integer;
|
||||||
|
begin
|
||||||
|
for J := 0 to High(beforeID) do
|
||||||
|
if (beforeID[J] = 0) and (fFontData[aFont].fAtlases[J].TexID <> 0)
|
||||||
|
and fFontData[aFont].fAtlases[J].GpuCurrent then
|
||||||
|
begin
|
||||||
|
seen := False;
|
||||||
|
for K := 0 to High(newIDs) do
|
||||||
|
if (newIDs[K].Font = aFont) and (newIDs[K].AtlasIndex = J)
|
||||||
|
and (newIDs[K].TexID = fFontData[aFont].fAtlases[J].TexID) then
|
||||||
|
begin
|
||||||
|
seen := True;
|
||||||
|
Break;
|
||||||
|
end;
|
||||||
|
if not seen then
|
||||||
|
begin
|
||||||
|
newID.Font := aFont;
|
||||||
|
newID.AtlasIndex := J;
|
||||||
|
newID.TexID := fFontData[aFont].fAtlases[J].TexID;
|
||||||
|
SetLength(newIDs, Length(newIDs) + 1);
|
||||||
|
newIDs[High(newIDs)] := newID;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure CleanupNewIDs;
|
||||||
|
var
|
||||||
|
J, fontIndex, atlasIndex: Integer;
|
||||||
|
savedID: Cardinal;
|
||||||
|
begin
|
||||||
|
for J := High(newIDs) downto 0 do
|
||||||
|
begin
|
||||||
|
fontIndex := Ord(newIDs[J].Font);
|
||||||
|
atlasIndex := newIDs[J].AtlasIndex;
|
||||||
|
if (atlasIndex >= 0) and (atlasIndex < Length(fFontData[newIDs[J].Font].fAtlases))
|
||||||
|
and (fFontData[newIDs[J].Font].fAtlases[atlasIndex].TexID = newIDs[J].TexID)
|
||||||
|
and fFontData[newIDs[J].Font].fAtlases[atlasIndex].GpuCurrent then
|
||||||
|
begin
|
||||||
|
savedID := newIDs[J].TexID;
|
||||||
|
if TKMRender.TextureBackendReady then
|
||||||
|
try
|
||||||
|
TKMRender.DeleteTexture(savedID);
|
||||||
|
except
|
||||||
|
// Preserve the original LoadFonts failure while continuing safe cleanup.
|
||||||
|
end;
|
||||||
|
fFontData[newIDs[J].Font].fAtlases[atlasIndex].TexID := 0;
|
||||||
|
fFontData[newIDs[J].Font].fAtlases[atlasIndex].GpuCurrent := False;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure ReleaseAttemptPixels;
|
||||||
|
var
|
||||||
|
FontIndex, AtlasIndex: Integer;
|
||||||
|
begin
|
||||||
|
for FontIndex := Ord(Low(TKMFont)) to Ord(High(TKMFont)) do
|
||||||
|
for AtlasIndex := 0 to High(fFontData[TKMFont(FontIndex)].fAtlases) do
|
||||||
|
SetLength(fFontData[TKMFont(FontIndex)].fAtlases[AtlasIndex].TexData, 0);
|
||||||
|
end;
|
||||||
|
|
||||||
|
begin
|
||||||
|
for F := Low(TKMFont) to High(TKMFont) do
|
||||||
|
begin
|
||||||
|
reuseCandidate[F].CanReuse := False;
|
||||||
|
reuseCandidate[F].PriorAtlas0ID := 0;
|
||||||
|
end;
|
||||||
|
if (aLoadLevel = fllFull) and HasContentLevel(fllMinimal)
|
||||||
|
and (fLoadLevel = fllMinimal) and TKMRender.TextureBackendReady then
|
||||||
|
for F := Low(TKMFont) to High(TKMFont) do
|
||||||
|
if (Length(fFontData[F].fAtlases) > 0)
|
||||||
|
and (fFontData[F].fAtlases[0].TexID <> 0)
|
||||||
|
and fFontData[F].fAtlases[0].GpuCurrent then
|
||||||
|
begin
|
||||||
|
reuseCandidate[F].CanReuse := True;
|
||||||
|
reuseCandidate[F].PriorAtlas0ID := fFontData[F].fAtlases[0].TexID;
|
||||||
|
end;
|
||||||
|
|
||||||
|
fContentSetValid := False;
|
||||||
|
fLoadLevel := fllMinimal;
|
||||||
|
decodedCount := 0;
|
||||||
|
try
|
||||||
|
for F := Low(TKMFont) to High(TKMFont) do
|
||||||
|
begin
|
||||||
|
relativeIdentity := 'data/gfx/fonts/' + FontTypeName[F] + '.' + TKMFontSpec.DEFAULT_EXT;
|
||||||
|
fntRef := KaMResolvePackagedResource(relativeIdentity);
|
||||||
|
fFontData[F].LoadFontX(fntRef, aLoadLevel, reuseCandidate[F].CanReuse,
|
||||||
|
reuseCandidate[F].PriorAtlas0ID);
|
||||||
|
Inc(decodedCount);
|
||||||
|
if decodedCount = Ord(High(TKMFont)) - Ord(Low(TKMFont)) + 1 then
|
||||||
|
begin
|
||||||
|
fLoadLevel := aLoadLevel;
|
||||||
|
fContentSetValid := True;
|
||||||
|
end;
|
||||||
|
|
||||||
|
if TKMRender.TextureBackendReady then
|
||||||
|
begin
|
||||||
|
SetLength(beforeID, fFontData[F].fAtlasCount);
|
||||||
|
for I := 0 to High(beforeID) do
|
||||||
|
beforeID[I] := fFontData[F].fAtlases[I].TexID;
|
||||||
|
try
|
||||||
|
fFontData[F].GenerateTextures(FONT_INFO[F].TexMode);
|
||||||
|
finally
|
||||||
|
RecordCreatedIDs(F);
|
||||||
|
end;
|
||||||
|
if aLoadLevel = fllMinimal then
|
||||||
|
atlasCount := 1
|
||||||
|
else
|
||||||
|
atlasCount := fFontData[F].fAtlasCount;
|
||||||
|
seen := True;
|
||||||
|
for I := 0 to atlasCount - 1 do
|
||||||
|
if (fFontData[F].fAtlases[I].TexID = 0)
|
||||||
|
or not fFontData[F].fAtlases[I].GpuCurrent then
|
||||||
|
seen := False;
|
||||||
|
if seen then
|
||||||
|
fFontData[F].Compact;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
except
|
||||||
|
CleanupNewIDs;
|
||||||
|
ReleaseAttemptPixels;
|
||||||
|
raise;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TKMResFonts.HasContentLevel(aRequiredLevel: TKMFontLoadLevel): Boolean;
|
||||||
|
begin
|
||||||
|
if not fContentSetValid then
|
||||||
|
Exit(False);
|
||||||
|
|
||||||
|
case fLoadLevel of
|
||||||
|
fllFull: Result := (aRequiredLevel = fllFull) or (aRequiredLevel = fllMinimal);
|
||||||
|
fllMinimal: Result := aRequiredLevel = fllMinimal;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
function TKMResFonts.HasGPUAtlasLevel(aRequiredLevel: TKMFontLoadLevel): Boolean;
|
||||||
|
var
|
||||||
|
F: TKMFont;
|
||||||
|
I, requiredAtlasCount: Integer;
|
||||||
|
begin
|
||||||
|
if not TKMRender.TextureBackendReady or not HasContentLevel(aRequiredLevel) then
|
||||||
|
Exit(False);
|
||||||
|
|
||||||
|
for F := Low(TKMFont) to High(TKMFont) do
|
||||||
|
begin
|
||||||
|
if (fFontData[F] = nil) or (fFontData[F].fAtlasCount < 1)
|
||||||
|
or (Length(fFontData[F].fAtlases) < fFontData[F].fAtlasCount) then
|
||||||
|
Exit(False);
|
||||||
|
|
||||||
|
if aRequiredLevel = fllMinimal then
|
||||||
|
requiredAtlasCount := 1
|
||||||
|
else
|
||||||
|
requiredAtlasCount := fFontData[F].fAtlasCount;
|
||||||
|
|
||||||
|
for I := 0 to requiredAtlasCount - 1 do
|
||||||
|
if (fFontData[F].fAtlases[I].TexID = 0)
|
||||||
|
or not fFontData[F].fAtlases[I].GpuCurrent then
|
||||||
|
Exit(False);
|
||||||
|
end;
|
||||||
|
|
||||||
|
Result := True;
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
function TKMResFonts.EnsureGPUAtlasLevel(aRequiredLevel: TKMFontLoadLevel): Boolean;
|
||||||
|
begin
|
||||||
|
if HasGPUAtlasLevel(aRequiredLevel) then
|
||||||
|
Exit(True);
|
||||||
|
if not TKMRender.TextureBackendReady then
|
||||||
|
Exit(False);
|
||||||
|
|
||||||
|
LoadFonts(aRequiredLevel);
|
||||||
|
Result := HasGPUAtlasLevel(aRequiredLevel);
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
procedure TKMResFonts.InvalidateGPUAtlasContext;
|
||||||
|
var
|
||||||
|
F: TKMFont;
|
||||||
|
I: Integer;
|
||||||
|
begin
|
||||||
|
for F := Low(TKMFont) to High(TKMFont) do
|
||||||
|
if fFontData[F] <> nil then
|
||||||
|
for I := 0 to High(fFontData[F].fAtlases) do
|
||||||
|
begin
|
||||||
|
fFontData[F].fAtlases[I].GpuCurrent := False;
|
||||||
|
fFontData[F].fAtlases[I].TexID := 0;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
function TKMFontSpec.GetLineHeight: SmallInt;
|
||||||
|
begin
|
||||||
|
Result := BaseHeight + LineSpacing;
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
function TKMFontSpec.GetCharWidth(aChar: WideChar; aConsiderEolSymbol: Boolean = False): Integer;
|
||||||
|
begin
|
||||||
|
if (not aConsiderEolSymbol and (aChar = #124)) or (aChar = #9) then
|
||||||
|
Result := 0
|
||||||
|
else
|
||||||
|
if aChar = #32 then
|
||||||
|
Result := WordSpacing
|
||||||
|
else
|
||||||
|
Result := GetLetter(aChar).Width + CharSpacing;
|
||||||
|
|
||||||
|
// CharSpacing could be negative
|
||||||
|
Result := Max(0, Result);
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
function TKMFontSpec.WordWrap(aText: UnicodeString; aMaxPxWidth: Integer; aForced: Boolean; aIndentAfterNL: Boolean;
|
||||||
|
aTabWidth: Integer = FONT_TAB_WIDTH): UnicodeString;
|
||||||
|
const
|
||||||
|
INDENT = ' ';
|
||||||
|
var
|
||||||
|
I, lineWrapPos: Integer;
|
||||||
|
lastWrappable: Integer;
|
||||||
|
lastWrappableIsSpace: Boolean;
|
||||||
|
lastColorMarkup, afterWrapClMarkup: UnicodeString;
|
||||||
|
dx, prevX: Integer;
|
||||||
|
tmpColor: Integer;
|
||||||
|
begin
|
||||||
|
Assert(aMaxPxWidth > 0);
|
||||||
|
|
||||||
|
if aText = '' then Exit ('');
|
||||||
|
|
||||||
|
dx := 0;
|
||||||
|
prevX := 0;
|
||||||
|
lastWrappable := -1;
|
||||||
|
lastWrappableIsSpace := False;
|
||||||
|
lastColorMarkup := '';
|
||||||
|
afterWrapClMarkup := '';
|
||||||
|
|
||||||
|
I := 1;
|
||||||
|
while I <= Length(aText) do
|
||||||
|
begin
|
||||||
|
//Chinese/Japanese characters (not punctuation) can always be wrapped before
|
||||||
|
//Check this before we update dx since we are allowing wrapping before this char
|
||||||
|
if ((Ord(aText[I]) >= 19968) and (Ord(aText[I]) <= 40870))
|
||||||
|
or ((Ord(aText[I]) >= $3040) and (Ord(aText[I]) <= $30ff)) then
|
||||||
|
begin
|
||||||
|
lastWrappable := I;
|
||||||
|
afterWrapClMarkup := lastColorMarkup;
|
||||||
|
prevX := dx; //dx does not include this char yet, since we are wrapping before it
|
||||||
|
lastWrappableIsSpace := False;
|
||||||
|
end;
|
||||||
|
|
||||||
|
//Ignore color markups [$FFFFFF][]
|
||||||
|
if (aText[I] = '[') and (I+1 <= Length(aText)) and (aText[I+1] = ']') then
|
||||||
|
begin
|
||||||
|
lastColorMarkup := '';
|
||||||
|
Inc(I); //Skip past end of color markup
|
||||||
|
end else
|
||||||
|
if (aText[I] = '[') and (I+8 <= Length(aText))
|
||||||
|
and (aText[I+1] = '$') and (aText[I+8] = ']')
|
||||||
|
and TryStrToInt(Copy(aText, I+1, 7), tmpColor) then
|
||||||
|
begin
|
||||||
|
lastColorMarkup := Copy(aText, I, 9);
|
||||||
|
Inc(I,8); //Skip past start of color markup
|
||||||
|
end else if (aText[I] = #9) then
|
||||||
|
dx := (Floor(dx / aTabWidth) + 1) * aTabWidth
|
||||||
|
else
|
||||||
|
Inc(dx, GetCharWidth(aText[I]));
|
||||||
|
|
||||||
|
if SysUtils.CharInSet(aText[I], [#9,#32,#124]) then
|
||||||
|
begin
|
||||||
|
lastWrappable := I;
|
||||||
|
afterWrapClMarkup := lastColorMarkup;
|
||||||
|
prevX := dx;
|
||||||
|
lastWrappableIsSpace := True;
|
||||||
|
end;
|
||||||
|
|
||||||
|
//This algorithm is not perfect, somehow line width is not within SizeX, but very rare
|
||||||
|
if ((dx > aMaxPxWidth) and (lastWrappable <> -1)) or (aText[I] = #124) then
|
||||||
|
begin
|
||||||
|
if lastWrappableIsSpace then
|
||||||
|
aText[lastWrappable] := #124 //Replace last whitespace with EOL
|
||||||
|
else begin
|
||||||
|
Inc(lastWrappable);
|
||||||
|
Insert(#124, aText, lastWrappable); //Insert EOL after last wrappable char
|
||||||
|
end;
|
||||||
|
|
||||||
|
if afterWrapClMarkup <> '' then
|
||||||
|
begin
|
||||||
|
Insert('[]', aText, lastWrappable);
|
||||||
|
Inc(I, 2);
|
||||||
|
Inc(lastWrappable, 2);
|
||||||
|
end;
|
||||||
|
|
||||||
|
if (aText[I] <> #124) and aIndentAfterNL then
|
||||||
|
begin
|
||||||
|
Insert(INDENT, aText, lastWrappable+1);
|
||||||
|
Inc(I, Length(INDENT));
|
||||||
|
Inc(dx, Length(INDENT) * WordSpacing);
|
||||||
|
end;
|
||||||
|
|
||||||
|
if afterWrapClMarkup <> '' then
|
||||||
|
begin
|
||||||
|
Insert(afterWrapClMarkup, aText, lastWrappable+1);
|
||||||
|
Inc(I, Length(afterWrapClMarkup));
|
||||||
|
end;
|
||||||
|
|
||||||
|
Dec(dx, prevX); //Subtract width since replaced whitespace
|
||||||
|
|
||||||
|
lastWrappable := -1;
|
||||||
|
end;
|
||||||
|
//Force an EOL part way through a word
|
||||||
|
if aForced and (dx > aMaxPxWidth) and (lastWrappable = -1) then
|
||||||
|
begin
|
||||||
|
Insert(#124, aText, I); //Insert an EOL before this character
|
||||||
|
dx := 0;
|
||||||
|
|
||||||
|
if lastColorMarkup <> '' then
|
||||||
|
begin
|
||||||
|
Insert('[]', aText, I);
|
||||||
|
Inc(I, 2);
|
||||||
|
end;
|
||||||
|
|
||||||
|
lineWrapPos := I;
|
||||||
|
|
||||||
|
if aIndentAfterNL then
|
||||||
|
begin
|
||||||
|
Insert(INDENT, aText, I+1);
|
||||||
|
Inc(I, Length(INDENT));
|
||||||
|
Inc(dx, Length(INDENT) * WordSpacing);
|
||||||
|
end;
|
||||||
|
|
||||||
|
if lastColorMarkup <> '' then
|
||||||
|
begin
|
||||||
|
Insert(lastColorMarkup, aText, lineWrapPos+1);
|
||||||
|
Inc(I, Length(lastColorMarkup));
|
||||||
|
end;
|
||||||
|
|
||||||
|
lastWrappable := -1;
|
||||||
|
end;
|
||||||
|
Inc(I);
|
||||||
|
end;
|
||||||
|
Result := aText;
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
function TKMFontSpec.CharsThatFit(const aText: UnicodeString; aMaxPxWidth: Integer; aRound: Boolean = False;
|
||||||
|
aConsiderEolSymbol: Boolean = False; aTabWidth: Integer = FONT_TAB_WIDTH): Integer;
|
||||||
|
var
|
||||||
|
I, dx, prevX, lastCharW: Integer;
|
||||||
|
begin
|
||||||
|
dx := 0;
|
||||||
|
Result := Length(aText);
|
||||||
|
|
||||||
|
for I := 1 to Length(aText) do
|
||||||
|
begin
|
||||||
|
lastCharW := GetCharWidth(aText[I], aConsiderEolSymbol);
|
||||||
|
prevX := dx;
|
||||||
|
if aText[I] = #9 then
|
||||||
|
dx := (Floor(dx / aTabWidth) + 1) * aTabWidth
|
||||||
|
else
|
||||||
|
Inc(dx, lastCharW);
|
||||||
|
|
||||||
|
if (dx > aMaxPxWidth) then
|
||||||
|
begin
|
||||||
|
// If we want to get approximate result, then check if total width is closer to prev width or to current
|
||||||
|
if aRound and (dx - aMaxPxWidth < aMaxPxWidth - prevX) then
|
||||||
|
Result := I
|
||||||
|
else
|
||||||
|
Result := I - 1; //Previous character fits, this one does not
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
function TKMFontSpec.GetTextSize(const aText: UnicodeString; aCountMarkup: Boolean = False; aConsiderEolSymbol: Boolean = False;
|
||||||
|
aTabWidth: Integer = FONT_TAB_WIDTH): TKMPoint;
|
||||||
|
var
|
||||||
|
lineCount: Integer;
|
||||||
|
begin
|
||||||
|
Result := GetTextSize(aText, lineCount, aCountMarkup, aConsiderEolSymbol, aTabWidth);
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
function TKMFontSpec.GetTextSize(const aText: UnicodeString; var aLineCount: Integer; aCountMarkup: Boolean = False;
|
||||||
|
aConsiderEolSymbol: Boolean = False; aTabWidth: Integer = FONT_TAB_WIDTH): TKMPoint;
|
||||||
|
var
|
||||||
|
I: Integer;
|
||||||
|
lineWidthInc, tmpColor: Integer;
|
||||||
|
lineWidth: array of Integer; // Some fonts may have negative CharSpacing
|
||||||
|
begin
|
||||||
|
Result.X := 0;
|
||||||
|
Result.Y := 0;
|
||||||
|
|
||||||
|
if aText = '' then Exit;
|
||||||
|
|
||||||
|
aLineCount := 1;
|
||||||
|
if not aConsiderEolSymbol then
|
||||||
|
for I := 1 to Length(aText) do
|
||||||
|
if aText[I] = #124 then Inc(aLineCount);
|
||||||
|
|
||||||
|
SetLength(lineWidth, aLineCount+2); //1..n+1 (for last line)
|
||||||
|
|
||||||
|
aLineCount := 1;
|
||||||
|
I := 1;
|
||||||
|
while I <= Length(aText) do
|
||||||
|
begin
|
||||||
|
lineWidthInc := 0;
|
||||||
|
if aCountMarkup then
|
||||||
|
begin
|
||||||
|
//Count all characters including markup
|
||||||
|
if aText[I] = #9 then // Tab char
|
||||||
|
lineWidthInc := (Floor(lineWidth[aLineCount] / aTabWidth) + 1) * aTabWidth - lineWidth[aLineCount]
|
||||||
|
else
|
||||||
|
lineWidthInc := GetCharWidth(aText[I], aConsiderEolSymbol);
|
||||||
|
Inc(lineWidth[aLineCount], lineWidthInc);
|
||||||
|
end else
|
||||||
|
//Ignore color markups [$FFFFFF][]
|
||||||
|
if (aText[I]='[') and (I+1 <= Length(aText)) and (aText[I+1]=']') then
|
||||||
|
Inc(I) //Skip past this markup
|
||||||
|
else
|
||||||
|
if (aText[I]='[') and (I+8 <= Length(aText))
|
||||||
|
and (aText[I+1] = '$') and (aText[I+8]=']')
|
||||||
|
and TryStrToInt(Copy(aText, I+1, 7), tmpColor) then
|
||||||
|
Inc(I,8) //Skip past this markup
|
||||||
|
else begin
|
||||||
|
//Not markup so count width normally
|
||||||
|
if aText[I] = #9 then // Tab char
|
||||||
|
lineWidthInc := (Floor(lineWidth[aLineCount] / aTabWidth) + 1) * aTabWidth - lineWidth[aLineCount]
|
||||||
|
else
|
||||||
|
lineWidthInc := GetCharWidth(aText[I], aConsiderEolSymbol);
|
||||||
|
Inc(lineWidth[aLineCount], lineWidthInc);
|
||||||
|
end;
|
||||||
|
|
||||||
|
if (not aConsiderEolSymbol and (aText[I] = #124)) or (I = Length(aText)) then
|
||||||
|
begin // If EOL or aText end
|
||||||
|
if aText[I] <> #9 then // for Tab reduce line width for CharSpacing and also for TAB 'jump'
|
||||||
|
lineWidthInc := 0;
|
||||||
|
lineWidth[aLineCount] := Math.Max(0, lineWidth[aLineCount] - CharSpacing - lineWidthInc);
|
||||||
|
// Remove last interletter space and negate double EOLs
|
||||||
|
Inc(aLineCount);
|
||||||
|
end;
|
||||||
|
Inc(I);
|
||||||
|
end;
|
||||||
|
|
||||||
|
Dec(aLineCount);
|
||||||
|
Result.Y := LineHeight * aLineCount;
|
||||||
|
for I := 1 to aLineCount do
|
||||||
|
Result.X := Math.Max(Result.X, lineWidth[I]);
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
// Return maximum of the width of specified strings when printed on screen with specified font.
|
||||||
|
function TKMFontSpec.GetMaxPrintWidthOfStrings(aStrings: array of string): Integer;
|
||||||
|
var
|
||||||
|
I: Integer;
|
||||||
|
begin
|
||||||
|
Result := 0;
|
||||||
|
for I := Low(aStrings) to High(aStrings) do
|
||||||
|
Result := Max(Result, GetTextSize(aStrings[I]).X);
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
end.
|
||||||
@@ -26,6 +26,9 @@ type
|
|||||||
TKMResource = class
|
TKMResource = class
|
||||||
private
|
private
|
||||||
fDataState: TResourceLoadState;
|
fDataState: TResourceLoadState;
|
||||||
|
fRequiredFontLevel: TKMFontLoadLevel;
|
||||||
|
fFontCompletionGeneration: QWord;
|
||||||
|
fFontCompletionValid: Boolean;
|
||||||
|
|
||||||
fCursors: TKMResCursors;
|
fCursors: TKMResCursors;
|
||||||
fFonts: TKMResFonts;
|
fFonts: TKMResFonts;
|
||||||
@@ -43,6 +46,7 @@ type
|
|||||||
procedure EnsureGameResourceInterpolation;
|
procedure EnsureGameResourceInterpolation;
|
||||||
procedure StepRefresh;
|
procedure StepRefresh;
|
||||||
procedure StepCaption(const aCaption: UnicodeString);
|
procedure StepCaption(const aCaption: UnicodeString);
|
||||||
|
procedure InvalidateFontCompletion;
|
||||||
public
|
public
|
||||||
OnLoadingStep: TKMEvent;
|
OnLoadingStep: TKMEvent;
|
||||||
OnLoadingText: TUnicodeStringEvent;
|
OnLoadingText: TUnicodeStringEvent;
|
||||||
@@ -59,6 +63,10 @@ type
|
|||||||
procedure LoadGameResources(aAlphaShadows: Boolean; aForceReload: Boolean = False);
|
procedure LoadGameResources(aAlphaShadows: Boolean; aForceReload: Boolean = False);
|
||||||
procedure StartGameSpriteRecovery;
|
procedure StartGameSpriteRecovery;
|
||||||
procedure LoadLocaleFonts(const aLocale: AnsiString; aLoadFullFonts: Boolean);
|
procedure LoadLocaleFonts(const aLocale: AnsiString; aLoadFullFonts: Boolean);
|
||||||
|
function FontResourcesReadyForGeneration(aGeneration: QWord): Boolean;
|
||||||
|
procedure InvalidateFontGraphicsContext;
|
||||||
|
function EnsureFontResourcesForGeneration(aCandidateGeneration: QWord;
|
||||||
|
constref aLiveAttemptGeneration: QWord): Boolean;
|
||||||
|
|
||||||
property DataState: TResourceLoadState read fDataState;
|
property DataState: TResourceLoadState read fDataState;
|
||||||
property GameResourcesReady: Boolean read GetGameResourcesReady;
|
property GameResourcesReady: Boolean read GetGameResourcesReady;
|
||||||
@@ -100,6 +108,9 @@ begin
|
|||||||
inherited Create;
|
inherited Create;
|
||||||
|
|
||||||
fDataState := rlsNone;
|
fDataState := rlsNone;
|
||||||
|
fRequiredFontLevel := fllMinimal;
|
||||||
|
fFontCompletionGeneration := 0;
|
||||||
|
fFontCompletionValid := False;
|
||||||
gLog.AddTime('Resource loading state - None');
|
gLog.AddTime('Resource loading state - None');
|
||||||
|
|
||||||
OnLoadingStep := aOnLoadingStep;
|
OnLoadingStep := aOnLoadingStep;
|
||||||
@@ -256,21 +267,95 @@ begin
|
|||||||
// Locale info is needed for DAT export and font loading
|
// Locale info is needed for DAT export and font loading
|
||||||
LoadLocaleResources(aLocale);
|
LoadLocaleResources(aLocale);
|
||||||
|
|
||||||
StepCaption('Reading fonts ...');
|
|
||||||
fFonts := TKMResFonts.Create;
|
|
||||||
if aLoadFullFonts or gResLocales.LocaleByCode(aLocale).NeedsFullFonts then
|
if aLoadFullFonts or gResLocales.LocaleByCode(aLocale).NeedsFullFonts then
|
||||||
fFonts.LoadFonts(fllFull)
|
fRequiredFontLevel := fllFull
|
||||||
else
|
else
|
||||||
fFonts.LoadFonts(fllMinimal);
|
fRequiredFontLevel := fllMinimal;
|
||||||
|
InvalidateFontCompletion;
|
||||||
|
|
||||||
|
StepCaption('Reading fonts ...');
|
||||||
|
FreeAndNil(fFonts);
|
||||||
|
fFonts := TKMResFonts.Create;
|
||||||
|
fFonts.LoadFonts(fRequiredFontLevel);
|
||||||
gLog.AddTime('Read fonts is done');
|
gLog.AddTime('Read fonts is done');
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
||||||
procedure TKMResource.LoadLocaleFonts(const aLocale: AnsiString; aLoadFullFonts: Boolean);
|
procedure TKMResource.LoadLocaleFonts(const aLocale: AnsiString; aLoadFullFonts: Boolean);
|
||||||
|
var
|
||||||
|
previouslyConfirmedGeneration: QWord;
|
||||||
begin
|
begin
|
||||||
if (Fonts.LoadLevel <> fllFull)
|
if not (aLoadFullFonts or gResLocales.LocaleByCode(aLocale).NeedsFullFonts) then
|
||||||
and (aLoadFullFonts or gResLocales.LocaleByCode(aLocale).NeedsFullFonts) then
|
Exit;
|
||||||
Fonts.LoadFonts(fllFull);
|
|
||||||
|
fRequiredFontLevel := fllFull;
|
||||||
|
if fFonts = nil then
|
||||||
|
Exit;
|
||||||
|
|
||||||
|
if fFonts.HasContentLevel(fllFull) then
|
||||||
|
Exit;
|
||||||
|
|
||||||
|
previouslyConfirmedGeneration := fFontCompletionGeneration;
|
||||||
|
InvalidateFontCompletion;
|
||||||
|
fFonts.LoadFonts(fllFull);
|
||||||
|
|
||||||
|
if (previouslyConfirmedGeneration <> 0)
|
||||||
|
and (fFontCompletionGeneration = previouslyConfirmedGeneration)
|
||||||
|
and fFonts.HasContentLevel(fllFull)
|
||||||
|
and fFonts.HasGPUAtlasLevel(fllFull) then
|
||||||
|
fFontCompletionValid := True;
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
procedure TKMResource.InvalidateFontCompletion;
|
||||||
|
begin
|
||||||
|
fFontCompletionValid := False;
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
function TKMResource.FontResourcesReadyForGeneration(aGeneration: QWord): Boolean;
|
||||||
|
begin
|
||||||
|
Result := False;
|
||||||
|
if (aGeneration = 0) or not fFontCompletionValid
|
||||||
|
or (fFontCompletionGeneration <> aGeneration) or (fFonts = nil) then
|
||||||
|
Exit;
|
||||||
|
|
||||||
|
Result := fFonts.HasContentLevel(fRequiredFontLevel)
|
||||||
|
and fFonts.HasGPUAtlasLevel(fRequiredFontLevel);
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
procedure TKMResource.InvalidateFontGraphicsContext;
|
||||||
|
begin
|
||||||
|
fFontCompletionGeneration := 0;
|
||||||
|
fFontCompletionValid := False;
|
||||||
|
if fFonts <> nil then
|
||||||
|
fFonts.InvalidateGPUAtlasContext;
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
function TKMResource.EnsureFontResourcesForGeneration(aCandidateGeneration: QWord;
|
||||||
|
constref aLiveAttemptGeneration: QWord): Boolean;
|
||||||
|
begin
|
||||||
|
InvalidateFontCompletion;
|
||||||
|
Result := False;
|
||||||
|
if (aCandidateGeneration = 0)
|
||||||
|
or (aLiveAttemptGeneration <> aCandidateGeneration)
|
||||||
|
or (fFonts = nil) then
|
||||||
|
Exit;
|
||||||
|
|
||||||
|
if not fFonts.EnsureGPUAtlasLevel(fRequiredFontLevel) then
|
||||||
|
Exit;
|
||||||
|
|
||||||
|
if (aLiveAttemptGeneration <> aCandidateGeneration)
|
||||||
|
or (fFonts = nil)
|
||||||
|
or not fFonts.HasContentLevel(fRequiredFontLevel)
|
||||||
|
or not fFonts.HasGPUAtlasLevel(fRequiredFontLevel) then
|
||||||
|
Exit;
|
||||||
|
|
||||||
|
fFontCompletionGeneration := aCandidateGeneration;
|
||||||
|
fFontCompletionValid := True;
|
||||||
|
Result := True;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user