This commit is contained in:
zueuk 2006-09-06 12:34:00 +00:00
parent 8289fc9b91
commit 0d6de238b2
5 changed files with 221 additions and 123 deletions

View File

@ -35,6 +35,7 @@ type
TBaseRenderer = class TBaseRenderer = class
private private
FOnProgress: TOnProgress; FOnProgress: TOnProgress;
strOutput: TStrings;
protected protected
camX0, camX1, camY0, camY1, // camera bounds camX0, camX1, camY0, camY1, // camera bounds
@ -54,7 +55,6 @@ type
FStop: integer;//boolean; FStop: integer;//boolean;
FImageMaker: TImageMaker; FImageMaker: TImageMaker;
strOutput: TStrings;
ColorMap: TColorMapArray; ColorMap: TColorMapArray;
@ -71,11 +71,10 @@ type
FMinBatches: integer; FMinBatches: integer;
FRenderOver: boolean; FRenderOver: boolean;
RenderTime: TDateTime; RenderTime, PauseTime: TDateTime;
procedure Progress(value: double); procedure Progress(value: double);
procedure SetNumThreads(const n: integer);
procedure SetMinDensity(const q: double); procedure SetMinDensity(const q: double);
procedure CreateColorMap; virtual; procedure CreateColorMap; virtual;
@ -94,6 +93,9 @@ type
procedure ClearBuckets; virtual; abstract; procedure ClearBuckets; virtual; abstract;
procedure RenderMM; procedure RenderMM;
procedure Trace(const str: string);
procedure TimeTrace(const str: string);
public public
constructor Create; virtual; constructor Create; virtual;
destructor Destroy; override; destructor Destroy; override;
@ -107,11 +109,14 @@ type
procedure SaveImage(const FileName: String); procedure SaveImage(const FileName: String);
procedure Stop; virtual; procedure Stop; virtual;
procedure Break; virtual; procedure BreakRender; virtual;
procedure Pause; virtual; abstract; procedure Pause; virtual;
procedure UnPause; virtual; abstract; procedure UnPause; virtual;
procedure GetBucketStats(var Stats: TBucketStats); function Failed: boolean;
procedure ShowBigStats;
procedure ShowSmallStats;
property OnProgress: TOnProgress property OnProgress: TOnProgress
read FOnProgress read FOnProgress
@ -125,7 +130,7 @@ type
read FSlice; read FSlice;
property NumThreads: integer property NumThreads: integer
read FNumThreads read FNumThreads
write SetNumThreads; write FNumThreads;
property Output: TStrings property Output: TStrings
write strOutput; write strOutput;
property MinDensity: double property MinDensity: double
@ -134,7 +139,6 @@ type
write FRenderOver; write FRenderOver;
end; end;
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
{ TRenderer } { TRenderer }
@ -150,7 +154,6 @@ type
FCP: TControlPoint; FCP: TControlPoint;
FMaxMem: int64; FMaxMem: int64;
public public
destructor Destroy; override; destructor Destroy; override;
@ -210,14 +213,49 @@ begin
FCP := Cp.Clone; FCP := Cp.Clone;
end; end;
///////////////////////////////////////////////////////////////////////////////
procedure TBaseRenderer.Trace(const str: string);
begin
if assigned(strOutput) then
strOutput.Add(str);
end;
procedure TBaseRenderer.TimeTrace(const str: string);
begin
if assigned(strOutput) then
strOutput.Add(TimeToStr(Now) + ' : ' + str);
end;
///////////////////////////////////////////////////////////////////////////////
procedure TBaseRenderer.Pause;
begin
PauseTime := Now;
TimeTrace('Pausing render');
end;
procedure TBaseRenderer.UnPause;
var
tNow: TDateTime;
begin
tNow := Now;
RenderTime := RenderTime + (tNow - PauseTime);
TimeTrace('Resuming render');
end;
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
procedure TBaseRenderer.Stop; procedure TBaseRenderer.Stop;
begin begin
TimeTrace('Terminating render');
FStop := 1; //True; FStop := 1; //True;
end; end;
procedure TBaseRenderer.Break; procedure TBaseRenderer.BreakRender;
begin begin
TimeTrace('Stopping render');
FStop := -1; FStop := -1;
end; end;
@ -228,12 +266,6 @@ begin
FOnprogress(Value); FOnprogress(Value);
end; end;
///////////////////////////////////////////////////////////////////////////////
procedure TBaseRenderer.SetNumThreads(const n: integer);
begin
FNumThreads := n;
end;
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
procedure TBaseRenderer.SetMinDensity(const q: double); procedure TBaseRenderer.SetMinDensity(const q: double);
begin begin
@ -242,11 +274,54 @@ begin
end; end;
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
procedure TBaseRenderer.GetBucketStats(var Stats: TBucketStats); function TBaseRenderer.Failed: boolean;
begin begin
Result := (FStop > 0);
end;
///////////////////////////////////////////////////////////////////////////////
procedure TBaseRenderer.ShowBigStats;
var
Stats: TBucketStats;
TotalSamples: int64;
begin
if not assigned(strOutput) then exit;
strOutput.Add('');
if NrSlices = 1 then
strOutput.Add('Render Statistics:')
else
strOutput.Add('Render Statistics for the last slice:'); // not really useful :-\
TotalSamples := int64(FNumBatches) * SUB_BATCH_SIZE; // * fcp.nbatches ?
if TotalSamples <= 0 then begin
strOutput.Add(' Nothing to talk about!'); // normally shouldn't happen
exit;
end;
strOutput.Add(Format(' Max possible bits: %2.3f', [8 + log2(TotalSamples)]));
FImageMaker.GetBucketStats(Stats); FImageMaker.GetBucketStats(Stats);
Stats.TotalSamples := int64(FNumBatches) * SUB_BATCH_SIZE; // * fcp.nbatches ? with Stats do begin
Stats.RenderTime := RenderTime; strOutput.Add(Format(' Max Red: %2.3f bits', [log2(MaxR)]));
strOutput.Add(Format(' Max Green: %2.3f bits', [log2(MaxG)]));
strOutput.Add(Format(' Max Blue: %2.3f bits', [log2(MaxB)]));
strOutput.Add(Format(' Max Count: %2.3f bits', [log2(MaxA)]));
strOutput.Add(Format(' Point hit ratio: %2.2f%%', [100.0*(TotalA/TotalSamples)]));
if RenderTime > 0 then // hmm
strOutput.Add(Format(' Average speed: %n points per second', [TotalSamples / (RenderTime * 24 * 60 * 60)]));
strOutput.Add(' Pure rendering time:' + TimeToString(RenderTime));
end;
end;
procedure TBaseRenderer.ShowSmallStats;
var
TotalSamples: int64;
begin
if not assigned(strOutput) then exit;
TotalSamples := int64(FNumBatches) * SUB_BATCH_SIZE; // * fcp.nbatches ?
if RenderTime > 0 then // hmm
strOutput.Add(Format(' Average speed: %n points per second', [TotalSamples / (RenderTime * 24 * 60 * 60)]));
strOutput.Add(' Pure rendering time:' + TimeToString(RenderTime));
end; end;
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
@ -264,11 +339,13 @@ end;
function TBaseRenderer.GetTransparentImage: TPngObject; function TBaseRenderer.GetTransparentImage: TPngObject;
begin begin
if FStop > 0 then begin if FStop > 0 then begin
assert(false); Trace('WARNING: Trying to get unprepared image!?');
FImageMaker.OnProgress := OnProgress; Result := nil;
FImageMaker.CreateImage; // FImageMaker.OnProgress := OnProgress;
end; // FImageMaker.CreateImage;
Result := FImageMaker.GetTransparentImage; end
else
Result := FImageMaker.GetTransparentImage;
end; end;
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
@ -292,13 +369,11 @@ end;
procedure TBaseRenderer.SaveImage(const FileName: String); procedure TBaseRenderer.SaveImage(const FileName: String);
begin begin
if FStop > 0 then begin if FStop > 0 then begin
if Assigned(strOutput) then TimeTrace(Format('Creating image with quality = %f', [fcp.actual_density]));
strOutput.Add(TimeToStr(Now) + Format(' : Creating image with quality = %f', [fcp.actual_density]));
FImageMaker.OnProgress := OnProgress; FImageMaker.OnProgress := OnProgress;
FImageMaker.CreateImage; FImageMaker.CreateImage;
end; end;
if Assigned(strOutput) then TimeTrace('Saving image');
strOutput.Add(TimeToStr(Now) + ' : Saving image');
FImageMaker.SaveImage(FileName); FImageMaker.SaveImage(FileName);
end; end;
@ -433,31 +508,28 @@ end;
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
procedure TBaseRenderer.InitBuffers; procedure TBaseRenderer.InitBuffers;
const
error_string = 'ERROR: Not enough memory for this render!';
var var
w, h, bits: integer; bits: integer;
begin begin
bits := GetBits; bits := GetBits;
w := BucketWidth;
h := BucketHeight;
CalcBufferSize; CalcBufferSize;
try try
if Assigned(strOutput) then TimeTrace(Format('Allocating %n Mb of memory', [BucketSize * SizeOfBucket[bits] / 1048576]));
strOutput.Add(TimeToStr(Now) +
Format(' : Allocating %n Mb of memory', [BucketSize * SizeOfBucket[bits] / 1048576]));
AllocateBuckets; // SetLength(buckets, BucketHeight, BucketWidth); // hmm :-/ AllocateBuckets; // SetLength(buckets, BucketHeight, BucketWidth);
except except
on EOutOfMemory do begin on EOutOfMemory do begin
if Assigned(strOutput) then if Assigned(strOutput) then
strOutput.Add('Error: not enough memory for this render!') strOutput.Add(error_string)
else else
Application.MessageBox('Error: not enough memory for this render!', 'Apophysis', 48); Application.MessageBox(error_string, 'Apophysis', 48);
BucketWidth := 0; BucketWidth := 0;
BucketHeight := 0; BucketHeight := 0;
FStop := 1; //true; FStop := 1;
exit; exit;
end; end;
end; end;
@ -475,7 +547,6 @@ begin
FImageMaker.SetCP(FCP); FImageMaker.SetCP(FCP);
FImageMaker.Init; FImageMaker.Init;
InitBuffers; InitBuffers;
if FStop <> 0 then exit; // memory allocation error? if FStop <> 0 then exit; // memory allocation error?
@ -490,12 +561,11 @@ begin
RenderTime := Now - RenderTime; RenderTime := Now - RenderTime;
if FStop <= 0 then begin if FStop <= 0 then begin
if Assigned(strOutput) then begin if fcp.sample_density = fcp.actual_density then
if fcp.sample_density = fcp.actual_density then TimeTrace('Creating image')
strOutput.Add(TimeToStr(Now) + ' : Creating image') else
else TimeTrace(Format('Creating image with quality = %f', [fcp.actual_density]));
strOutput.Add(TimeToStr(Now) + Format(' : Creating image with quality = %f', [fcp.actual_density]));
end;
FImageMaker.OnProgress := OnProgress; FImageMaker.OnProgress := OnProgress;
FImageMaker.CreateImage; FImageMaker.CreateImage;
end; end;
@ -577,7 +647,7 @@ begin
RenderTime := RenderTime + (Now - t); RenderTime := RenderTime + (Now - t);
if FStop = 0 then begin if FStop = 0 then begin
if Assigned(strOutput) then strOutput.Add(TimeToStr(Now) + ' : Creating image'); TimeTrace('Creating image');
FImageMaker.OnProgress := OnProgress; FImageMaker.OnProgress := OnProgress;
FImageMaker.CreateImage(Slice * fcp.height); FImageMaker.CreateImage(Slice * fcp.height);
end; end;
@ -614,13 +684,6 @@ begin
FCP := CP; FCP := CP;
end; end;
{
///////////////////////////////////////////////////////////////////////////////
constructor TRenderer.Create;
begin
end;
}
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
procedure TRenderer.Render; procedure TRenderer.Render;
begin begin
@ -628,15 +691,14 @@ begin
FRenderer.Free; FRenderer.Free;
assert(Fmaxmem=0); assert(Fmaxmem=0);
if FMaxMem = 0 then begin // if FMaxMem = 0 then begin
FRenderer := TRenderer32.Create; FRenderer := TRenderer32.Create;
end else begin // end else begin
FRenderer := TRenderer32MM.Create; // FRenderer := TRenderer32MM.Create;
FRenderer.MaxMem := FMaxMem // FRenderer.MaxMem := FMaxMem
end; // end;
FRenderer.SetCP(FCP); FRenderer.SetCP(FCP);
// FRenderer.compatibility := compatibility;
FRenderer.OnProgress := FOnProgress; FRenderer.OnProgress := FOnProgress;
FRenderer.Render; FRenderer.Render;
end; end;
@ -648,24 +710,5 @@ begin
FRenderer.Stop; FRenderer.Stop;
end; end;
{
procedure TRenderer.UpdateImage(CP: TControlPoint);
begin
end;
procedure TRenderer.SaveImage(const FileName: String);
begin
if assigned(FRenderer) then
FRenderer.SaveImage(FileName);
end;
procedure TRenderer.GetBucketStats(var Stats: TBucketStats);
begin
if assigned(FRenderer) then
FRenderer.GetBucketStats(Stats);
end;
}
end. end.

View File

@ -45,7 +45,7 @@ type
public public
procedure Stop; override; procedure Stop; override;
procedure Break; override; procedure BreakRender; override;
procedure Pause; override; procedure Pause; override;
procedure UnPause; override; procedure UnPause; override;
@ -66,12 +66,10 @@ var
nSamples: Int64; nSamples: Int64;
bc : integer; bc : integer;
begin begin
if strOutput <> nil then begin if FNumSlices > 1 then
if FNumSlices > 1 then TimeTrace(Format('Rendering slice #%d of %d...', [FSlice + 1, FNumSlices]))
strOutput.Add(TimeToStr(Now) + Format(' : Rendering slice #%d...', [FSlice + 1])) else
else TimeTrace('Rendering...');
strOutput.Add(TimeToStr(Now) + ' : Rendering...');
end;
nSamples := Round(sample_density * NrSlices * BucketSize / (oversample * oversample)); nSamples := Round(sample_density * NrSlices * BucketSize / (oversample * oversample));
FNumBatches := Round(nSamples / (fcp.nbatches * SUB_BATCH_SIZE)); FNumBatches := Round(nSamples / (fcp.nbatches * SUB_BATCH_SIZE));
@ -134,7 +132,7 @@ begin
inherited; // FStop := 1; inherited; // FStop := 1;
end; end;
procedure TBaseMTRenderer.Break; procedure TBaseMTRenderer.BreakRender;
var var
i: integer; i: integer;
begin begin
@ -151,6 +149,8 @@ procedure TBaseMTRenderer.Pause;
var var
i: integer; i: integer;
begin begin
inherited;
for i := 0 to High(WorkingThreads) do for i := 0 to High(WorkingThreads) do
WorkingThreads[i].Suspend; WorkingThreads[i].Suspend;
end; end;
@ -159,6 +159,8 @@ procedure TBaseMTRenderer.UnPause;
var var
i: integer; i: integer;
begin begin
inherited;
for i := 0 to High(WorkingThreads) do for i := 0 to High(WorkingThreads) do
WorkingThreads[i].Resume; WorkingThreads[i].Resume;
end; end;

View File

@ -75,12 +75,10 @@ var
nsamples: int64; nsamples: int64;
IterateBatchProc: procedure of object; IterateBatchProc: procedure of object;
begin begin
if Assigned(strOutput) then begin if FNumSlices > 1 then
if FNumSlices > 1 then TimeTrace(Format('Rendering slice #%d of %d...', [FSlice + 1, FNumSlices]))
strOutput.Add(TimeToStr(Now) + Format(' : Rendering slice #%d...', [FSlice + 1])) else
else TimeTrace('Rendering...');
strOutput.Add(TimeToStr(Now) + ' : Rendering...');
end;
Randomize; Randomize;
@ -105,7 +103,7 @@ begin
for i := 0 to FNumBatches-1 do begin for i := 0 to FNumBatches-1 do begin
if FStop <> 0 then begin if FStop <> 0 then begin
// if (FStop < 0) or (i >= FMinBatches) then begin //? // if (FStop <> 0) or (i >= FMinBatches) then begin //?
fcp.actual_density := fcp.actual_density + fcp.actual_density := fcp.actual_density +
fcp.sample_density * i / FNumBatches; // actual quality of incomplete render fcp.sample_density * i / FNumBatches; // actual quality of incomplete render
FNumBatches := i; FNumBatches := i;

View File

@ -27,7 +27,7 @@ uses
Global, RenderTypes, PngImage, Global, RenderTypes, PngImage,
Render64, Render64MT, Render64, Render64MT,
Render48, Render48MT, Render48, Render48MT,
Render32, Render32MT, Render32, Render32MT,
Render32f, Render32fMT; Render32f, Render32fMT;
const const
@ -41,7 +41,6 @@ type
FOnProgress: TOnProgress; FOnProgress: TOnProgress;
FCP: TControlPoint; FCP: TControlPoint;
// Fcompatibility: Integer;
FMaxMem: int64; FMaxMem: int64;
FNrThreads: Integer; FNrThreads: Integer;
FBitsPerSample: integer; FBitsPerSample: integer;
@ -51,11 +50,10 @@ type
procedure CreateRenderer; procedure CreateRenderer;
function GetNrSlices: integer; function GetNrSlices: integer;
function GetSlice: integer; function GetSlice: integer;
// procedure Setcompatibility(const Value: Integer);
// procedure SetMaxMem(const Value: int64);
// procedure SetNrThreads(const Value: Integer);
procedure SetBitsPerSample(const bits: Integer); procedure SetBitsPerSample(const bits: Integer);
procedure Trace(const str: string);
public public
TargetHandle: HWND; TargetHandle: HWND;
WaitForMore, More: boolean; WaitForMore, More: boolean;
@ -74,9 +72,11 @@ type
procedure Terminate; procedure Terminate;
procedure Suspend; procedure Suspend;
procedure Resume; procedure Resume;
procedure Break; procedure BreakRender;
procedure GetBucketStats(var Stats: TBucketStats); // procedure GetBucketStats(var Stats: TBucketStats);
procedure ShowBigStats;
procedure ShowSmallStats;
property OnProgress: TOnProgress property OnProgress: TOnProgress
read FOnProgress read FOnProgress
@ -105,7 +105,8 @@ type
implementation implementation
uses uses
Math, Sysutils; Math, SysUtils,
Tracer;
{ TRenderThread } { TRenderThread }
@ -116,6 +117,8 @@ begin
FRenderer.Free; FRenderer.Free;
FRenderer := nil; FRenderer := nil;
if assigned(FCP) then FCP.Free;
inherited; inherited;
end; end;
@ -138,7 +141,7 @@ end;
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
procedure TRenderThread.SetCP(CP: TControlPoint); procedure TRenderThread.SetCP(CP: TControlPoint);
begin begin
FCP := CP; FCP := CP.Clone;
end; end;
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
@ -155,8 +158,11 @@ end;
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
procedure TRenderThread.CreateRenderer; procedure TRenderThread.CreateRenderer;
begin begin
if assigned(FRenderer) then if assigned(FRenderer) then begin
Trace('Destroying previous renderer (?)');
FRenderer.Free; FRenderer.Free;
end;
Trace('Creating renderer');
if NrThreads <= 1 then begin if NrThreads <= 1 then begin
if MaxMem = 0 then begin if MaxMem = 0 then begin
@ -201,9 +207,6 @@ begin
FRenderer.MinDensity := FMinDensity; FRenderer.MinDensity := FMinDensity;
FRenderer.OnProgress := FOnProgress; FRenderer.OnProgress := FOnProgress;
FRenderer.Output := FOutput; FRenderer.Output := FOutput;
// FRenderer.Render;
//?... if FRenderer.Failed then Terminate; // hmm
end; end;
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
@ -213,21 +216,32 @@ begin
CreateRenderer; CreateRenderer;
RenderMore: RenderMore:
assert(assigned(FRenderer));
Trace('Rendering');
FRenderer.Render; FRenderer.Render;
if Terminated then begin if Terminated or FRenderer.Failed then begin
PostMessage(TargetHandle, WM_THREAD_TERMINATE, 0, 0); Trace('Sending WM_THREAD_TERMINATE');
PostMessage(TargetHandle, WM_THREAD_TERMINATE, 0, ThreadID);
Trace('Terminated');
exit; exit;
end end
else PostMessage(TargetHandle, WM_THREAD_COMPLETE, 0, 0); else begin
Trace('Sending WM_THREAD_COMPLETE');
PostMessage(TargetHandle, WM_THREAD_COMPLETE, 0, ThreadID);
end;
if WaitForMore and (FRenderer <> nil) then begin if WaitForMore and (FRenderer <> nil) then begin
FRenderer.RenderMore := true; FRenderer.RenderMore := true;
Trace('Waiting for more');
inherited Suspend; inherited Suspend;
if WaitForMore then goto RenderMore; if WaitForMore then goto RenderMore;
end; end;
Trace('Finished');
end; end;
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
@ -243,24 +257,22 @@ end;
procedure TRenderThread.Suspend; procedure TRenderThread.Suspend;
begin begin
if NrThreads > 1 then if assigned(FRenderer) then FRenderer.Pause;
if assigned(FRenderer) then FRenderer.Pause;
inherited; inherited;
end; end;
procedure TRenderThread.Resume; procedure TRenderThread.Resume;
begin begin
if NrThreads > 1 then if assigned(FRenderer) then FRenderer.UnPause;
if assigned(FRenderer) then FRenderer.UnPause;
inherited; inherited;
end; end;
procedure TRenderThread.Break; procedure TRenderThread.BreakRender;
begin begin
if assigned(FRenderer) then if assigned(FRenderer) then
FRenderer.Break; FRenderer.BreakRender;
end; end;
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
@ -303,10 +315,24 @@ begin
end; end;
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
procedure TRenderThread.GetBucketStats(var Stats: TBucketStats); procedure TRenderThread.Trace(const str: string);
begin begin
if assigned(FRenderer) then if assigned(FOutput) and (TraceLevel >= 2) then
FRenderer.GetBucketStats(Stats); FOutput.Add('. . > RenderThread #' + IntToStr(ThreadID) + ': ' + str);
end; end;
///////////////////////////////////////////////////////////////////////////////
procedure TRenderThread.ShowBigStats;
begin
if assigned(FRenderer) then
FRenderer.ShowBigStats;
end;
procedure TRenderThread.ShowSmallStats;
begin
if assigned(FRenderer) then
FRenderer.ShowSmallStats;
end;
///////////////////////////////////////////////////////////////////////////////
end. end.

View File

@ -11,7 +11,6 @@ type
Red, Red,
Green, Green,
Blue: integer; //Int64; Blue: integer; //Int64;
// Count: Int64;
end; end;
PColorMapColor = ^TColorMapColor; PColorMapColor = ^TColorMapColor;
TColorMapArray = array[0..255] of TColorMapColor; TColorMapArray = array[0..255] of TColorMapColor;
@ -81,10 +80,40 @@ const
type type
TBucketStats = record TBucketStats = record
MaxR, MaxG, MaxB, MaxA, MaxR, MaxG, MaxB, MaxA,
TotalA, TotalSamples: int64; TotalA: int64;
RenderTime: TDateTime;
end; end;
function TimeToString(t: TDateTime): string;
implementation implementation
uses SysUtils;
function TimeToString(t: TDateTime): string;
var
n: integer;
begin
n := Trunc(t);
Result := '';
if n>0 then begin
Result := Result + Format(' %d day', [n]);
if (n mod 10) <> 1 then Result := Result + 's';
end;
t := t * 24;
n := Trunc(t) mod 24;
if n>0 then begin
Result := Result + Format(' %d hour', [n]);
if (n mod 10) <> 1 then Result := Result + 's';
end;
t := t * 60;
n := Trunc(t) mod 60;
if n>0 then begin
Result := Result + Format(' %d minute', [n]);
if (n mod 10) <> 1 then Result := Result + 's';
end;
t := t * 60;
t := t - (Trunc(t) div 60) * 60;
Result := Result + Format(' %.2f seconds', [t]);
end;
end. end.