Add high-FPS render pacing and telemetry
Some checks failed
build / Windows Build (push) Has been cancelled
Some checks failed
build / Windows Build (push) Has been cancelled
This commit is contained in:
307
docs/anti-cheat-architecture-2026.md
Normal file
307
docs/anti-cheat-architecture-2026.md
Normal file
@@ -0,0 +1,307 @@
|
||||
# Anti-Cheat Architecture 2026
|
||||
|
||||
This document proposes a practical anti-cheat design for the current Metin2
|
||||
client and server stack.
|
||||
|
||||
Date of analysis: 2026-04-16
|
||||
|
||||
## Executive Summary
|
||||
|
||||
The current client contains only a weak integrity layer:
|
||||
|
||||
- `src/UserInterface/ProcessCRC.cpp` computes CRC values for the running binary.
|
||||
- `src/UserInterface/PythonNetworkStream.cpp` calls `BuildProcessCRC()` when the
|
||||
client enters select phase.
|
||||
- `src/UserInterface/PythonNetworkStreamPhaseGame.cpp` injects two CRC bytes
|
||||
into attack packets.
|
||||
- `src/UserInterface/PythonNetworkStreamPhaseGame.cpp` contains a `CG::HACK`
|
||||
reporting path.
|
||||
- `src/UserInterface/ProcessScanner.cpp` exists, but it is not wired into the
|
||||
live runtime.
|
||||
- `src/UserInterface/PythonNetworkStreamPhaseGame.cpp::__SendCRCReportPacket()`
|
||||
is currently a stub that returns `true`.
|
||||
|
||||
This is not a production-grade anti-cheat. It raises the bar slightly for basic
|
||||
binary tampering, but it does not defend well against modern external tools,
|
||||
automation, packet abuse, or VM-assisted cheats.
|
||||
|
||||
The recommended 2026 design is:
|
||||
|
||||
1. Make the server authoritative for critical combat and movement rules.
|
||||
2. Add telemetry and delayed-ban workflows.
|
||||
3. Add a commercial client anti-cheat as a hardening layer, not as the primary
|
||||
trust model.
|
||||
4. Treat botting and farming as a separate abuse problem with separate signals.
|
||||
|
||||
## Goals
|
||||
|
||||
- Stop speedhack, combohack, rangehack, teleport, packet spam, and most forms of
|
||||
client-side state forgery.
|
||||
- Raise the cost of memory editing, DLL injection, and automation.
|
||||
- Reduce false positives by moving final judgment to server-side evidence.
|
||||
- Keep the design compatible with a legacy custom client.
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- Absolute cheat prevention.
|
||||
- Fully trusting a third-party kernel driver to solve game logic abuse.
|
||||
- Real-time automatic bans on a single weak signal.
|
||||
|
||||
## Current State
|
||||
|
||||
### What exists today
|
||||
|
||||
- Client self-CRC:
|
||||
- `src/UserInterface/ProcessCRC.cpp`
|
||||
- CRC pieces attached to outgoing attack packets:
|
||||
- `src/UserInterface/PythonNetworkStreamPhaseGame.cpp`
|
||||
- Hack message queue and packet transport:
|
||||
- `src/UserInterface/PythonNetworkStreamPhaseGame.cpp`
|
||||
|
||||
### What is missing today
|
||||
|
||||
- No active process blacklist or scanner integration in the runtime path.
|
||||
- No complete CRC reporting flow to the server.
|
||||
- No server-authoritative verification model documented in the current client
|
||||
source tree.
|
||||
- No robust evidence pipeline for review, delayed bans, or behavioral scoring.
|
||||
|
||||
## Recommended Architecture
|
||||
|
||||
### Layer 1: Server-Authoritative Core
|
||||
|
||||
This is the most important layer. The server must become the source of truth for
|
||||
combat and movement outcomes.
|
||||
|
||||
### Movement validation
|
||||
|
||||
The server should validate:
|
||||
|
||||
- maximum velocity by actor state
|
||||
- acceleration spikes
|
||||
- position delta against elapsed server time
|
||||
- warp-only transitions
|
||||
- path continuity through collision or map rules
|
||||
- impossible Z transitions and fly transitions
|
||||
|
||||
Recommended outputs:
|
||||
|
||||
- hard reject for impossible moves
|
||||
- suspicion score for repeated marginal violations
|
||||
- session telemetry record
|
||||
|
||||
### Combat validation
|
||||
|
||||
The server should validate:
|
||||
|
||||
- attack rate by skill and weapon class
|
||||
- combo timing windows
|
||||
- skill cooldowns
|
||||
- victim distance and angle
|
||||
- hit count per server tick
|
||||
- target existence and visibility rules
|
||||
- animation-independent skill gate timing
|
||||
|
||||
This removes most of the value of classic combohack, attack speed manipulation,
|
||||
and packet replay.
|
||||
|
||||
### State validation
|
||||
|
||||
The server should own:
|
||||
|
||||
- HP / SP mutation rules
|
||||
- buff durations
|
||||
- item use success
|
||||
- teleport authorization
|
||||
- quest-critical transitions
|
||||
- loot generation and pickup eligibility
|
||||
|
||||
### Layer 2: Telemetry and Evidence
|
||||
|
||||
Do not ban on one event unless the event is impossible by design.
|
||||
|
||||
Collect per-session evidence for:
|
||||
|
||||
- movement violations
|
||||
- attack interval violations
|
||||
- repeated rejected packets
|
||||
- target distance anomalies
|
||||
- client build mismatch
|
||||
- CRC mismatch
|
||||
- anti-cheat vendor verdict
|
||||
- bot-like repetition and route loops
|
||||
|
||||
Recommended workflow:
|
||||
|
||||
- score each event
|
||||
- decay score over time
|
||||
- trigger thresholds for:
|
||||
- silent logging
|
||||
- shadow restrictions
|
||||
- temporary action blocks
|
||||
- GM review
|
||||
- delayed ban wave
|
||||
|
||||
Delayed bans are important because instant bans teach attackers which checks
|
||||
worked.
|
||||
|
||||
### Layer 3: Client Integrity
|
||||
|
||||
Use client integrity only as a supporting layer.
|
||||
|
||||
Recommended client responsibilities:
|
||||
|
||||
- verify binary and pack integrity
|
||||
- report build identifier and signed manifest identity
|
||||
- detect common injection or patching signals
|
||||
- report tampering and environment metadata
|
||||
- protect transport secrets and runtime config
|
||||
|
||||
Suggested upgrades to the current client:
|
||||
|
||||
- replace the current partial CRC path with a real signed build identity
|
||||
- sign client packs and executable manifests
|
||||
- complete `__SendCRCReportPacket()` and send useful integrity evidence
|
||||
- remove dead anti-cheat code or wire it fully
|
||||
- add secure telemetry batching instead of ad hoc string-only hack messages
|
||||
|
||||
### Layer 4: Commercial Anti-Cheat
|
||||
|
||||
### Recommended vendor order for this project
|
||||
|
||||
1. `XIGNCODE3`
|
||||
2. `BattlEye`
|
||||
3. `Denuvo Anti-Cheat`
|
||||
4. `Easy Anti-Cheat`
|
||||
|
||||
### Why this order
|
||||
|
||||
`XIGNCODE3` looks like the best functional fit for a legacy MMO client:
|
||||
|
||||
- public positioning is strongly focused on online PC games
|
||||
- public feature set includes macro detection, resource protection, and real
|
||||
time pattern updates
|
||||
- market fit appears closer to older custom launchers and non-mainstream engine
|
||||
stacks
|
||||
|
||||
`BattlEye` is a strong option if you want a more established premium PC
|
||||
anti-cheat and can support tighter integration work.
|
||||
|
||||
`Denuvo Anti-Cheat` is technically strong, but it is likely the heaviest vendor
|
||||
path in both integration and commercial terms.
|
||||
|
||||
`Easy Anti-Cheat` is attractive if budget is the main constraint, but it should
|
||||
not change the core rule: the server must still be authoritative.
|
||||
|
||||
### Layer 5: Anti-Bot and Economy Abuse
|
||||
|
||||
Botting should be treated as a separate control plane.
|
||||
|
||||
Recommended controls:
|
||||
|
||||
- route repetition heuristics
|
||||
- farming loop detection
|
||||
- vendor / warehouse / trade anomaly scoring
|
||||
- captcha or interaction challenge only after confidence threshold
|
||||
- account graph analysis for mule and funnel behavior
|
||||
- hardware and device clustering where legally appropriate
|
||||
|
||||
Do not rely on captcha alone. It is only a friction tool.
|
||||
|
||||
## Proposed Rollout
|
||||
|
||||
### Phase 1: Fix trust boundaries
|
||||
|
||||
- document which outcomes are currently trusted from the client
|
||||
- move movement and attack legality fully server-side
|
||||
- add structured telemetry records
|
||||
|
||||
Deliverable:
|
||||
|
||||
- server can reject impossible movement and impossible attack cadence without any
|
||||
client anti-cheat dependency
|
||||
|
||||
### Phase 2: Replace weak integrity
|
||||
|
||||
- complete binary and pack integrity reporting
|
||||
- version all reports by client build
|
||||
- bind reports to account, session, and channel
|
||||
|
||||
Deliverable:
|
||||
|
||||
- reliable client integrity evidence reaches the server
|
||||
|
||||
### Phase 3: Vendor pilot
|
||||
|
||||
- integrate one commercial anti-cheat in observe mode first
|
||||
- compare vendor verdicts with server suspicion score
|
||||
- review false positives before enforcement
|
||||
|
||||
Deliverable:
|
||||
|
||||
- enforcement policy based on combined evidence
|
||||
|
||||
### Phase 4: Ban and response pipeline
|
||||
|
||||
- implement delayed ban waves
|
||||
- implement silent risk tiers
|
||||
- implement GM review tooling
|
||||
|
||||
Deliverable:
|
||||
|
||||
- repeatable response process instead of ad hoc action
|
||||
|
||||
## Concrete Changes For This Codebase
|
||||
|
||||
Client-side work:
|
||||
|
||||
- `src/UserInterface/ProcessCRC.cpp`
|
||||
- replace simple CRC flow with signed manifest and richer integrity report
|
||||
- `src/UserInterface/PythonNetworkStream.cpp`
|
||||
- send integrity bootstrap at phase transition
|
||||
- `src/UserInterface/PythonNetworkStreamPhaseGame.cpp`
|
||||
- implement real CRC and integrity packet submission
|
||||
- replace free-form hack strings with typed evidence events
|
||||
- `src/UserInterface/ProcessScanner.cpp`
|
||||
- either remove dead code or reintroduce it as a fully designed subsystem
|
||||
|
||||
Server-side work:
|
||||
|
||||
- movement validator
|
||||
- combat cadence validator
|
||||
- anomaly scoring service
|
||||
- evidence storage
|
||||
- review and ban tooling
|
||||
|
||||
## Design Rules
|
||||
|
||||
- Never trust timing sent by the client for legality.
|
||||
- Never trust client-side cooldown completion.
|
||||
- Never trust client position as final truth for hit validation.
|
||||
- Never ban permanently on a single client-only signal.
|
||||
- Never ship anti-cheat changes without telemetry first.
|
||||
|
||||
## Success Metrics
|
||||
|
||||
- drop in successful speedhack and combohack reports
|
||||
- drop in impossible movement accepted by the server
|
||||
- reduced farm bot session length
|
||||
- reduced economy inflation from automated abuse
|
||||
- acceptable false positive rate during observe mode
|
||||
|
||||
## Recommended Next Step
|
||||
|
||||
Implement Layer 1 before vendor integration.
|
||||
|
||||
If the server still accepts impossible combat or movement, a commercial
|
||||
anti-cheat only increases attacker cost. It does not fix the trust model.
|
||||
|
||||
## External References
|
||||
|
||||
- Unity cheat prevention guidance
|
||||
- Epic Easy Anti-Cheat public developer material
|
||||
- BattlEye public developer material
|
||||
- Wellbia XIGNCODE3 public product material
|
||||
- Denuvo Anti-Cheat public product material
|
||||
- Metin2Dev discussions around server-side validation, anti-bot workflows, and
|
||||
weak community anti-cheat releases
|
||||
342
docs/high-fps-client-plan.md
Normal file
342
docs/high-fps-client-plan.md
Normal file
@@ -0,0 +1,342 @@
|
||||
# High-FPS Client Plan
|
||||
|
||||
This document describes how to move the current client from a hard 60 FPS model
|
||||
to a safe high-FPS model without breaking gameplay timing.
|
||||
|
||||
Date of analysis: 2026-04-16
|
||||
|
||||
## Executive Summary
|
||||
|
||||
The current client is not limited to 60 FPS by a single config value. It is
|
||||
limited by architecture.
|
||||
|
||||
Current hard constraints in the source:
|
||||
|
||||
- `src/UserInterface/PythonApplication.cpp`
|
||||
- constructor switches `CTimer` into custom time mode
|
||||
- main loop advances by a fixed `16/17 ms`
|
||||
- main loop sleeps until the next tick
|
||||
- `src/EterBase/Timer.cpp`
|
||||
- custom mode returns `16 + (m_index & 1)` milliseconds per frame
|
||||
- `src/GameLib/GameType.cpp`
|
||||
- `g_fGameFPS = 60.0f`
|
||||
- `src/GameLib/ActorInstanceMotion.cpp`
|
||||
- motion frame math depends on `g_fGameFPS`
|
||||
- `src/EterLib/GrpDevice.cpp`
|
||||
- presentation interval is also involved, especially in fullscreen
|
||||
|
||||
Because of this, a simple FPS unlock would likely produce one or more of these
|
||||
problems:
|
||||
|
||||
- broken combo timing
|
||||
- incorrect animation frame stepping
|
||||
- combat desync with the server
|
||||
- accelerated or jittery local effects
|
||||
- unstable camera or UI timing
|
||||
|
||||
The correct design is:
|
||||
|
||||
1. Keep simulation on a fixed tick.
|
||||
2. Decouple rendering from simulation.
|
||||
3. Add interpolation for render state.
|
||||
4. Expose render cap and VSync as separate options.
|
||||
|
||||
## Current Timing Model
|
||||
|
||||
### Main loop
|
||||
|
||||
In `src/UserInterface/PythonApplication.cpp`:
|
||||
|
||||
- `CTimer::Instance().UseCustomTime()` is enabled in the constructor.
|
||||
- `rkTimer.Advance()` advances simulated time.
|
||||
- `GetElapsedMilliecond()` returns a fixed `16/17 ms`.
|
||||
- `s_uiNextFrameTime += uiFrameTime` schedules the next frame.
|
||||
- `Sleep(rest)` enforces the cap.
|
||||
|
||||
This means the current process loop is effectively built around a fixed 60 Hz
|
||||
clock.
|
||||
|
||||
### Gameplay coupling
|
||||
|
||||
`src/GameLib/GameType.cpp` defines:
|
||||
|
||||
```cpp
|
||||
extern float g_fGameFPS = 60.0f;
|
||||
```
|
||||
|
||||
This value is used by motion and attack frame math in:
|
||||
|
||||
- `src/GameLib/ActorInstanceMotion.cpp`
|
||||
- `src/GameLib/RaceMotionData.cpp`
|
||||
|
||||
The result is that update timing and motion timing are coupled to 60 FPS.
|
||||
|
||||
## Design Target
|
||||
|
||||
Target architecture:
|
||||
|
||||
- simulation update: fixed 60 Hz
|
||||
- render: uncapped or capped to monitor / user value
|
||||
- interpolation: enabled between fixed simulation states
|
||||
- VSync: optional
|
||||
- gameplay legality: unchanged from the server point of view
|
||||
|
||||
This lets the client render at:
|
||||
|
||||
- 120 FPS
|
||||
- 144 FPS
|
||||
- 165 FPS
|
||||
- 240 FPS
|
||||
- uncapped
|
||||
|
||||
while still keeping game logic deterministic.
|
||||
|
||||
## Recommended Implementation
|
||||
|
||||
### Step 1: Replace the custom fixed timer for frame scheduling
|
||||
|
||||
Do not delete all timer code immediately. First isolate responsibilities.
|
||||
|
||||
Split timing into:
|
||||
|
||||
- simulation accumulator time
|
||||
- render frame delta
|
||||
- presentation pacing
|
||||
|
||||
Recommended source touchpoints:
|
||||
|
||||
- `src/EterBase/Timer.cpp`
|
||||
- `src/EterBase/Timer.h`
|
||||
- `src/UserInterface/PythonApplication.cpp`
|
||||
|
||||
Preferred clock source:
|
||||
|
||||
- `QueryPerformanceCounter` / `QueryPerformanceFrequency`
|
||||
- or a StepTimer-style wrapper with high-resolution real time
|
||||
|
||||
### Step 2: Convert the main loop to fixed update + variable render
|
||||
|
||||
Current model:
|
||||
|
||||
- one process pass
|
||||
- one fixed pseudo-frame
|
||||
- optional sleep
|
||||
|
||||
Target model:
|
||||
|
||||
```text
|
||||
read real delta
|
||||
accumulate delta
|
||||
while accumulator >= fixedTick:
|
||||
run simulation update
|
||||
accumulator -= fixedTick
|
||||
alpha = accumulator / fixedTick
|
||||
render(alpha)
|
||||
present
|
||||
optional sleep for render cap
|
||||
```
|
||||
|
||||
Recommended fixed tick:
|
||||
|
||||
- `16.666666 ms`
|
||||
|
||||
Recommended initial render caps:
|
||||
|
||||
- 60
|
||||
- 120
|
||||
- 144
|
||||
- 165
|
||||
- 240
|
||||
- unlimited
|
||||
|
||||
### Step 3: Keep gameplay update on fixed 60 Hz
|
||||
|
||||
Do not increase gameplay tick in the first rollout.
|
||||
|
||||
Keep these systems on the fixed update path:
|
||||
|
||||
- network processing that depends on gameplay state
|
||||
- actor motion update
|
||||
- attack state transitions
|
||||
- player movement simulation
|
||||
- effect simulation when tied to gameplay
|
||||
- camera state that depends on actor state
|
||||
|
||||
This reduces risk dramatically.
|
||||
|
||||
### Step 4: Render from interpolated state
|
||||
|
||||
The renderer must not directly depend on only the last fixed update result if
|
||||
you want smooth motion at high refresh rates.
|
||||
|
||||
Add previous and current renderable state for:
|
||||
|
||||
- actor transform
|
||||
- mount transform
|
||||
- camera transform
|
||||
- optionally important effect anchors
|
||||
|
||||
At render time:
|
||||
|
||||
- interpolate position
|
||||
- slerp or interpolate rotation where needed
|
||||
- use `alpha` from the simulation accumulator
|
||||
|
||||
Without this step, high-FPS output will still look like 60 FPS motion with extra
|
||||
duplicate frames.
|
||||
|
||||
### Step 5: Separate render cap from VSync
|
||||
|
||||
The current D3D present settings are not enough on their own.
|
||||
|
||||
Recommended behavior:
|
||||
|
||||
- windowed:
|
||||
- allow `immediate` plus optional software frame cap
|
||||
- fullscreen:
|
||||
- keep VSync configurable
|
||||
- do not rely on fullscreen `PresentationInterval` as the only limiter
|
||||
|
||||
Source touchpoint:
|
||||
|
||||
- `src/EterLib/GrpDevice.cpp`
|
||||
|
||||
### Step 6: Make `SetFPS()` real or remove it
|
||||
|
||||
`app.SetFPS()` exists today but only stores `m_iFPS`. It does not drive the main
|
||||
loop.
|
||||
|
||||
Source touchpoints:
|
||||
|
||||
- `src/UserInterface/PythonApplication.cpp`
|
||||
- `src/UserInterface/PythonApplication.h`
|
||||
- `src/UserInterface/PythonApplicationModule.cpp`
|
||||
|
||||
Required action:
|
||||
|
||||
- wire `m_iFPS` into the render pacing code
|
||||
- or rename the API to reflect the real behavior
|
||||
|
||||
### Step 7: Audit all 60 FPS assumptions
|
||||
|
||||
Search and review every system that assumes 60 FPS timing.
|
||||
|
||||
Known touchpoints:
|
||||
|
||||
- `src/GameLib/GameType.cpp`
|
||||
- `src/GameLib/ActorInstanceMotion.cpp`
|
||||
- `src/GameLib/RaceMotionData.cpp`
|
||||
- `src/AudioLib/MaSoundInstance.h`
|
||||
- `src/AudioLib/SoundEngine.cpp`
|
||||
- `src/AudioLib/Type.cpp`
|
||||
|
||||
Expected rule:
|
||||
|
||||
- gameplay timing stays fixed at 60 Hz for rollout 1
|
||||
- render-only timing becomes variable
|
||||
- audio smoothing constants may need review if they are implicitly tied to frame
|
||||
rate instead of elapsed time
|
||||
|
||||
## Rollout Plan
|
||||
|
||||
### Phase 1: Mechanical decoupling
|
||||
|
||||
- introduce high-resolution real timer
|
||||
- add accumulator-based fixed update loop
|
||||
- keep simulation at 60 Hz
|
||||
- render once per outer loop
|
||||
|
||||
Deliverable:
|
||||
|
||||
- no visible gameplay behavior change at 60 Hz cap
|
||||
|
||||
### Phase 2: Interpolation
|
||||
|
||||
- store previous and current render states
|
||||
- render with interpolation alpha
|
||||
- validate player, NPC, mount, and camera smoothness
|
||||
|
||||
Deliverable:
|
||||
|
||||
- motion looks smoother above 60 Hz
|
||||
|
||||
### Phase 3: Render settings
|
||||
|
||||
- implement `render_fps_limit`
|
||||
- implement `vsync` toggle
|
||||
- expose settings to Python and config
|
||||
|
||||
Deliverable:
|
||||
|
||||
- user-selectable 120 / 144 / 165 / 240 / unlimited
|
||||
|
||||
### Phase 4: Validation
|
||||
|
||||
Test matrix:
|
||||
|
||||
- 60 Hz monitor, VSync on and off
|
||||
- 144 Hz monitor, cap 60 / 144 / unlimited
|
||||
- minimized and alt-tab paths
|
||||
- crowded city combat
|
||||
- boss fight
|
||||
- mounted combat
|
||||
- loading transitions
|
||||
- packet-heavy scenes
|
||||
|
||||
Metrics:
|
||||
|
||||
- stable attack cadence
|
||||
- no combo timing regression
|
||||
- no movement speed regression
|
||||
- no animation stalls
|
||||
- no camera jitter
|
||||
- no CPU runaway when capped
|
||||
|
||||
## Risks
|
||||
|
||||
### High risk
|
||||
|
||||
- attack and combo logic tied to frame count
|
||||
- animation transitions tied to frame count
|
||||
- server-visible timing drift
|
||||
|
||||
### Medium risk
|
||||
|
||||
- effect systems that assume one render per update
|
||||
- audio systems with frame-based smoothing
|
||||
- UI code that assumes stable fixed frame cadence
|
||||
|
||||
### Lower risk
|
||||
|
||||
- D3D present configuration
|
||||
- config plumbing for user settings
|
||||
|
||||
## Minimal Safe First Patch
|
||||
|
||||
If the goal is the fastest safe path, the first implementation should do only
|
||||
this:
|
||||
|
||||
1. keep gameplay at fixed 60 Hz
|
||||
2. render more often
|
||||
3. interpolate actor and camera transforms
|
||||
4. expose render cap option
|
||||
|
||||
Do not attempt in the first patch:
|
||||
|
||||
- 120 Hz gameplay simulation
|
||||
- rewriting all motion logic to time-based math
|
||||
- changing server combat timing
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- client can render above 60 FPS on high refresh displays
|
||||
- gameplay remains server-compatible
|
||||
- no measurable change in combat legality
|
||||
- 60 FPS mode still behaves like the current client
|
||||
|
||||
## Recommended Next Step
|
||||
|
||||
Implement Phase 1 and Phase 2 together in a branch.
|
||||
|
||||
That is the smallest meaningful change set that can produce visibly smoother
|
||||
output without committing to a full time-based gameplay rewrite.
|
||||
@@ -65,6 +65,11 @@ VOID ELTimer_SetFrameMSec()
|
||||
gs_dwFrameTime = ELTimer_GetMSec();
|
||||
}
|
||||
|
||||
VOID ELTimer_SetFrameMSecValue(DWORD dwFrameTime)
|
||||
{
|
||||
gs_dwFrameTime = dwFrameTime;
|
||||
}
|
||||
|
||||
CTimer::CTimer()
|
||||
{
|
||||
ELTimer_Init();
|
||||
|
||||
@@ -38,4 +38,5 @@ VOID ELTimer_SetServerMSec(DWORD dwServerTime);
|
||||
DWORD ELTimer_GetServerMSec();
|
||||
|
||||
VOID ELTimer_SetFrameMSec();
|
||||
DWORD ELTimer_GetFrameMSec();
|
||||
VOID ELTimer_SetFrameMSecValue(DWORD dwFrameTime);
|
||||
DWORD ELTimer_GetFrameMSec();
|
||||
|
||||
@@ -18,6 +18,9 @@ void CActorInstance::INSTANCEBASE_Deform()
|
||||
|
||||
void CActorInstance::INSTANCEBASE_Transform()
|
||||
{
|
||||
m_v3InterpolationStartPosition = D3DXVECTOR3(m_x, m_y, m_z);
|
||||
m_fInterpolationStartRotation = m_fcurRotation;
|
||||
|
||||
if (m_pkHorse)
|
||||
{
|
||||
m_pkHorse->INSTANCEBASE_Transform();
|
||||
@@ -44,6 +47,30 @@ void CActorInstance::INSTANCEBASE_Transform()
|
||||
UpdateAttribute();
|
||||
}
|
||||
|
||||
void CActorInstance::ApplyRenderInterpolation(float fInterpolation)
|
||||
{
|
||||
const float fAlpha = std::max(0.0f, std::min(1.0f, fInterpolation));
|
||||
|
||||
D3DXVECTOR3 v3InterpolatedPosition;
|
||||
v3InterpolatedPosition.x = m_v3InterpolationStartPosition.x + (m_x - m_v3InterpolationStartPosition.x) * fAlpha;
|
||||
v3InterpolatedPosition.y = m_v3InterpolationStartPosition.y + (m_y - m_v3InterpolationStartPosition.y) * fAlpha;
|
||||
v3InterpolatedPosition.z = m_v3InterpolationStartPosition.z + (m_z - m_v3InterpolationStartPosition.z) * fAlpha;
|
||||
|
||||
const float fInterpolatedRotation = GetInterpolatedRotation(m_fInterpolationStartRotation, m_fcurRotation, fAlpha);
|
||||
|
||||
SetPosition(v3InterpolatedPosition);
|
||||
if (0.0f != m_rotX || 0.0f != m_rotY)
|
||||
{
|
||||
CGraphicObjectInstance::SetRotation(m_rotX, m_rotY, fInterpolatedRotation);
|
||||
}
|
||||
else
|
||||
{
|
||||
CGraphicObjectInstance::SetRotation(fInterpolatedRotation);
|
||||
}
|
||||
|
||||
Transform();
|
||||
}
|
||||
|
||||
|
||||
void CActorInstance::OnUpdate()
|
||||
{
|
||||
@@ -875,6 +902,7 @@ void CActorInstance::__InitializeRotationData()
|
||||
{
|
||||
m_fAtkDirRot = 0.0f;
|
||||
m_fcurRotation = 0.0f;
|
||||
m_fInterpolationStartRotation = 0.0f;
|
||||
m_rotBegin = 0.0f;
|
||||
m_rotEnd = 0.0f;
|
||||
m_rotEndTime = 0.0f;
|
||||
|
||||
@@ -425,6 +425,7 @@ class CActorInstance : public IActorInstance, public IFlyTargetableObject
|
||||
void LookAt(CActorInstance * pInstance);
|
||||
void LookWith(CActorInstance * pInstance);
|
||||
void LookAtFromXY(float x, float y, CActorInstance * pDestInstance);
|
||||
void ApplyRenderInterpolation(float fInterpolation);
|
||||
|
||||
|
||||
void SetReachScale(float fScale);
|
||||
@@ -767,6 +768,7 @@ class CActorInstance : public IActorInstance, public IFlyTargetableObject
|
||||
float m_x;
|
||||
float m_y;
|
||||
float m_z;
|
||||
D3DXVECTOR3 m_v3InterpolationStartPosition;
|
||||
D3DXVECTOR3 m_v3Pos;
|
||||
D3DXVECTOR3 m_v3Movement;
|
||||
BOOL m_bNeedUpdateCollision;
|
||||
@@ -779,6 +781,7 @@ class CActorInstance : public IActorInstance, public IFlyTargetableObject
|
||||
|
||||
// Rotation
|
||||
float m_fcurRotation;
|
||||
float m_fInterpolationStartRotation;
|
||||
float m_rotBegin;
|
||||
float m_rotEnd;
|
||||
float m_rotEndTime;
|
||||
|
||||
@@ -245,6 +245,15 @@ void CInstanceBase::SHORSE::Render()
|
||||
rkActor.Render();
|
||||
}
|
||||
|
||||
void CInstanceBase::SHORSE::ApplyRenderInterpolation(float fInterpolation)
|
||||
{
|
||||
if (!IsMounting())
|
||||
return;
|
||||
|
||||
CActorInstance& rkActor = GetActorRef();
|
||||
rkActor.ApplyRenderInterpolation(fInterpolation);
|
||||
}
|
||||
|
||||
void CInstanceBase::__AttachHorseSaddle()
|
||||
{
|
||||
if (!IsMountingHorse())
|
||||
@@ -1948,6 +1957,12 @@ void CInstanceBase::Transform()
|
||||
m_GraphicThingInstance.INSTANCEBASE_Transform();
|
||||
}
|
||||
|
||||
void CInstanceBase::ApplyRenderInterpolation(float fInterpolation)
|
||||
{
|
||||
m_kHorse.ApplyRenderInterpolation(fInterpolation);
|
||||
m_GraphicThingInstance.ApplyRenderInterpolation(fInterpolation);
|
||||
}
|
||||
|
||||
|
||||
void CInstanceBase::Deform()
|
||||
{
|
||||
|
||||
@@ -472,6 +472,7 @@ class CInstanceBase
|
||||
bool UpdateDeleting();
|
||||
|
||||
void Transform();
|
||||
void ApplyRenderInterpolation(float fInterpolation);
|
||||
void Deform();
|
||||
void Render();
|
||||
void RenderTrace();
|
||||
@@ -847,6 +848,7 @@ class CInstanceBase
|
||||
void SetMoveSpeed(UINT uMovSpd);
|
||||
void Deform();
|
||||
void Render();
|
||||
void ApplyRenderInterpolation(float fInterpolation);
|
||||
CActorInstance& GetActorRef();
|
||||
CActorInstance* GetActorPtr();
|
||||
|
||||
@@ -1139,4 +1141,4 @@ inline int RaceToSex(int race)
|
||||
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,12 +6,15 @@
|
||||
#include "EterGrnLib/Material.h"
|
||||
|
||||
#include "resource.h"
|
||||
#include "GameType.h"
|
||||
#include "PythonApplication.h"
|
||||
#include "PythonCharacterManager.h"
|
||||
|
||||
#include "ProcessScanner.h"
|
||||
|
||||
#include <utf8.h>
|
||||
#include <cstdarg>
|
||||
#include <cstdlib>
|
||||
|
||||
extern void GrannyCreateSharedDeformBuffer();
|
||||
extern void GrannyDestroySharedDeformBuffer();
|
||||
@@ -21,6 +24,91 @@ double g_specularSpd=0.007f;
|
||||
|
||||
CPythonApplication * CPythonApplication::ms_pInstance;
|
||||
|
||||
namespace
|
||||
{
|
||||
// Match the legacy custom timer cadence of alternating 17 ms and 16 ms.
|
||||
constexpr double kFixedUpdateMS = 16.5;
|
||||
constexpr double kMaxCatchUpDelayMS = 250.0;
|
||||
|
||||
float ClampInterpolationFactor(float fInterpolation)
|
||||
{
|
||||
return std::max(0.0f, std::min(1.0f, fInterpolation));
|
||||
}
|
||||
|
||||
D3DXVECTOR3 LerpVector3(const D3DXVECTOR3& c_rv3Begin, const D3DXVECTOR3& c_rv3End, float fInterpolation)
|
||||
{
|
||||
return D3DXVECTOR3(
|
||||
c_rv3Begin.x + (c_rv3End.x - c_rv3Begin.x) * fInterpolation,
|
||||
c_rv3Begin.y + (c_rv3End.y - c_rv3Begin.y) * fInterpolation,
|
||||
c_rv3Begin.z + (c_rv3End.z - c_rv3Begin.z) * fInterpolation);
|
||||
}
|
||||
|
||||
bool IsRenderTelemetryEnabledFromEnvironment()
|
||||
{
|
||||
const char* c_szValue = std::getenv("M2_RENDER_TELEMETRY");
|
||||
if (!c_szValue || !*c_szValue)
|
||||
return false;
|
||||
|
||||
if (0 == _stricmp(c_szValue, "1") || 0 == _stricmp(c_szValue, "true") || 0 == _stricmp(c_szValue, "yes") || 0 == _stricmp(c_szValue, "on"))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TryReadEnvironmentInt(const char* c_szName, int* piValue)
|
||||
{
|
||||
if (!piValue)
|
||||
return false;
|
||||
|
||||
const char* c_szValue = std::getenv(c_szName);
|
||||
if (!c_szValue || !*c_szValue)
|
||||
return false;
|
||||
|
||||
char* pEnd = nullptr;
|
||||
const long lValue = std::strtol(c_szValue, &pEnd, 10);
|
||||
if (pEnd == c_szValue || (pEnd && *pEnd))
|
||||
return false;
|
||||
|
||||
*piValue = static_cast<int>(lValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ResetRenderTelemetryTrace()
|
||||
{
|
||||
FILE* fp = nullptr;
|
||||
if (fopen_s(&fp, "log/render_telemetry.txt", "w") != 0 || !fp)
|
||||
return;
|
||||
|
||||
fprintf(fp, "render telemetry\n");
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
void AppendRenderTelemetryTrace(const char* c_szFormat, ...)
|
||||
{
|
||||
FILE* fp = nullptr;
|
||||
if (fopen_s(&fp, "log/render_telemetry.txt", "a") != 0 || !fp)
|
||||
return;
|
||||
|
||||
va_list args;
|
||||
va_start(args, c_szFormat);
|
||||
vfprintf(fp, c_szFormat, args);
|
||||
va_end(args);
|
||||
fputc('\n', fp);
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
void FormatRenderTargetFPSLabel(unsigned int iFPS, char* c_szBuffer, size_t uBufferSize)
|
||||
{
|
||||
if (!c_szBuffer || 0 == uBufferSize)
|
||||
return;
|
||||
|
||||
if (iFPS > 0)
|
||||
_snprintf_s(c_szBuffer, uBufferSize, _TRUNCATE, "%u", iFPS);
|
||||
else
|
||||
_snprintf_s(c_szBuffer, uBufferSize, _TRUNCATE, "MAX");
|
||||
}
|
||||
}
|
||||
|
||||
float c_fDefaultCameraRotateSpeed = 1.5f;
|
||||
float c_fDefaultCameraPitchSpeed = 1.5f;
|
||||
float c_fDefaultCameraZoomSpeed = 0.05f;
|
||||
@@ -35,9 +123,28 @@ m_poMouseHandler(NULL),
|
||||
m_dwUpdateFPS(0),
|
||||
m_dwRenderFPS(0),
|
||||
m_fAveRenderTime(0.0f),
|
||||
m_bRenderTelemetryEnabled(false),
|
||||
m_bRenderTelemetryHudVisible(false),
|
||||
m_dwRenderTelemetryIntervalMS(1000),
|
||||
m_dwRenderTelemetryWindowStartMS(0),
|
||||
m_dwRenderTelemetryLoopCount(0),
|
||||
m_dwRenderTelemetryUpdateCount(0),
|
||||
m_dwRenderTelemetryRenderCount(0),
|
||||
m_dwRenderTelemetryBlockedRenderCount(0),
|
||||
m_dwRenderTelemetryLastPresentTime(0),
|
||||
m_dwRenderTelemetryPresentGapSamples(0),
|
||||
m_fRenderTelemetryUpdateTimeSumMS(0.0),
|
||||
m_fRenderTelemetryRenderTimeSumMS(0.0),
|
||||
m_fRenderTelemetrySleepTimeSumMS(0.0),
|
||||
m_fRenderTelemetryInterpolationSum(0.0),
|
||||
m_fRenderTelemetryPresentGapSumMS(0.0),
|
||||
m_dwCurRenderTime(0),
|
||||
m_dwCurUpdateTime(0),
|
||||
m_dwLoad(0),
|
||||
m_dwFaceCount(0),
|
||||
m_fGlobalTime(0.0f),
|
||||
m_fGlobalElapsedTime(0.0f),
|
||||
m_fRenderInterpolationFactor(0.0f),
|
||||
m_dwLButtonDownTime(0),
|
||||
m_dwLastIdleTime(0),
|
||||
m_IsMovingMainWindow(false)
|
||||
@@ -54,12 +161,15 @@ m_IsMovingMainWindow(false)
|
||||
m_isWindowFullScreenEnable = FALSE;
|
||||
|
||||
m_v3CenterPosition = D3DXVECTOR3(0.0f, 0.0f, 0.0f);
|
||||
m_v3LastCenterPosition = m_v3CenterPosition;
|
||||
m_dwStartLocalTime = ELTimer_GetMSec();
|
||||
m_tServerTime = 0;
|
||||
m_tLocalStartTime = 0;
|
||||
|
||||
m_iPort = 0;
|
||||
m_iFPS = 60;
|
||||
m_iFPS = std::max(0, m_pySystem.GetRenderFPS());
|
||||
m_bRenderTelemetryHudVisible = m_pySystem.IsShowPerformanceHUD();
|
||||
InitializeRenderRuntimeOverrides();
|
||||
|
||||
m_isActivateWnd = false;
|
||||
m_isMinimizedWnd = true;
|
||||
@@ -168,6 +278,7 @@ void CPythonApplication::RenderGame()
|
||||
float fFarClip = m_pyBackground.GetFarClip();
|
||||
|
||||
m_pyGraphic.SetPerspective(30.0f, fAspect, 100.0, fFarClip);
|
||||
ApplyRenderInterpolation();
|
||||
|
||||
CCullingManager::Instance().Process();
|
||||
|
||||
@@ -256,10 +367,137 @@ void CPythonApplication::UpdateGame()
|
||||
SetCenterPosition(kPPosMainActor.x, kPPosMainActor.y, kPPosMainActor.z);
|
||||
}
|
||||
|
||||
void CPythonApplication::InitializeRenderRuntimeOverrides()
|
||||
{
|
||||
m_bRenderTelemetryEnabled = IsRenderTelemetryEnabledFromEnvironment();
|
||||
|
||||
int iIntervalMS = 0;
|
||||
if (TryReadEnvironmentInt("M2_RENDER_TELEMETRY_INTERVAL_MS", &iIntervalMS) && iIntervalMS > 0)
|
||||
m_dwRenderTelemetryIntervalMS = static_cast<DWORD>(std::min(iIntervalMS, 60000));
|
||||
|
||||
if (m_bRenderTelemetryEnabled)
|
||||
{
|
||||
ResetRenderTelemetryTrace();
|
||||
AppendRenderTelemetryTrace("enabled interval_ms=%lu", static_cast<unsigned long>(m_dwRenderTelemetryIntervalMS));
|
||||
}
|
||||
|
||||
int iRenderFPS = 0;
|
||||
if (TryReadEnvironmentInt("M2_RENDER_FPS", &iRenderFPS))
|
||||
{
|
||||
m_iFPS = std::max(0, iRenderFPS);
|
||||
if (m_bRenderTelemetryEnabled)
|
||||
AppendRenderTelemetryTrace("env_override target_fps=%u", m_iFPS);
|
||||
}
|
||||
|
||||
char szTargetFPS[16];
|
||||
FormatRenderTargetFPSLabel(m_iFPS, szTargetFPS, sizeof(szTargetFPS));
|
||||
m_stRenderTelemetrySummary = "Render telemetry\nTarget FPS: ";
|
||||
m_stRenderTelemetrySummary += szTargetFPS;
|
||||
m_stRenderTelemetrySummary += "\nCollecting frame pacing...";
|
||||
|
||||
if (IsRenderTelemetrySamplingEnabled())
|
||||
ResetRenderTelemetryWindow(ELTimer_GetMSec());
|
||||
}
|
||||
|
||||
void CPythonApplication::ResetRenderTelemetryWindow(DWORD dwNow)
|
||||
{
|
||||
m_dwRenderTelemetryWindowStartMS = dwNow;
|
||||
m_dwRenderTelemetryLoopCount = 0;
|
||||
m_dwRenderTelemetryUpdateCount = 0;
|
||||
m_dwRenderTelemetryRenderCount = 0;
|
||||
m_dwRenderTelemetryBlockedRenderCount = 0;
|
||||
m_dwRenderTelemetryPresentGapSamples = 0;
|
||||
m_fRenderTelemetryUpdateTimeSumMS = 0.0;
|
||||
m_fRenderTelemetryRenderTimeSumMS = 0.0;
|
||||
m_fRenderTelemetrySleepTimeSumMS = 0.0;
|
||||
m_fRenderTelemetryInterpolationSum = 0.0;
|
||||
m_fRenderTelemetryPresentGapSumMS = 0.0;
|
||||
}
|
||||
|
||||
void CPythonApplication::FlushRenderTelemetryWindow(DWORD dwNow)
|
||||
{
|
||||
if (!IsRenderTelemetrySamplingEnabled() || !m_dwRenderTelemetryWindowStartMS)
|
||||
return;
|
||||
|
||||
const DWORD dwWindowMS = std::max<DWORD>(1, dwNow - m_dwRenderTelemetryWindowStartMS);
|
||||
const double fWindowMS = static_cast<double>(dwWindowMS);
|
||||
const double fUpdateFPS = static_cast<double>(m_dwRenderTelemetryUpdateCount) * 1000.0 / fWindowMS;
|
||||
const double fRenderFPS = static_cast<double>(m_dwRenderTelemetryRenderCount) * 1000.0 / fWindowMS;
|
||||
const double fAvgUpdateMS = m_dwRenderTelemetryUpdateCount ? (m_fRenderTelemetryUpdateTimeSumMS / static_cast<double>(m_dwRenderTelemetryUpdateCount)) : 0.0;
|
||||
const double fAvgRenderMS = m_dwRenderTelemetryRenderCount ? (m_fRenderTelemetryRenderTimeSumMS / static_cast<double>(m_dwRenderTelemetryRenderCount)) : 0.0;
|
||||
const double fAvgSleepMS = m_dwRenderTelemetryLoopCount ? (m_fRenderTelemetrySleepTimeSumMS / static_cast<double>(m_dwRenderTelemetryLoopCount)) : 0.0;
|
||||
const double fAvgInterpolation = m_dwRenderTelemetryRenderCount ? (m_fRenderTelemetryInterpolationSum / static_cast<double>(m_dwRenderTelemetryRenderCount)) : 0.0;
|
||||
const double fAvgPresentGapMS = m_dwRenderTelemetryPresentGapSamples ? (m_fRenderTelemetryPresentGapSumMS / static_cast<double>(m_dwRenderTelemetryPresentGapSamples)) : 0.0;
|
||||
const DWORD dwElapsedMS = dwNow - m_dwStartLocalTime;
|
||||
char szTargetFPS[16];
|
||||
char szSummary[256];
|
||||
|
||||
FormatRenderTargetFPSLabel(m_iFPS, szTargetFPS, sizeof(szTargetFPS));
|
||||
_snprintf_s(
|
||||
szSummary,
|
||||
sizeof(szSummary),
|
||||
_TRUNCATE,
|
||||
"Render %.1f Update %.1f Target %s\nPresent %.1fms Sleep %.1fms CPU %.1f/%.1fms",
|
||||
fRenderFPS,
|
||||
fUpdateFPS,
|
||||
szTargetFPS,
|
||||
fAvgPresentGapMS,
|
||||
fAvgSleepMS,
|
||||
fAvgUpdateMS,
|
||||
fAvgRenderMS);
|
||||
m_stRenderTelemetrySummary = szSummary;
|
||||
|
||||
if (m_bRenderTelemetryEnabled)
|
||||
{
|
||||
AppendRenderTelemetryTrace(
|
||||
"elapsed_ms=%lu window_ms=%lu target_fps=%u update_fps=%.2f render_fps=%.2f avg_update_ms=%.2f avg_render_ms=%.2f avg_sleep_ms=%.2f avg_present_gap_ms=%.2f avg_interp=%.3f loops=%lu updates=%lu renders=%lu blocked=%lu cur_update_ms=%lu cur_render_ms=%lu",
|
||||
static_cast<unsigned long>(dwElapsedMS),
|
||||
static_cast<unsigned long>(dwWindowMS),
|
||||
m_iFPS,
|
||||
fUpdateFPS,
|
||||
fRenderFPS,
|
||||
fAvgUpdateMS,
|
||||
fAvgRenderMS,
|
||||
fAvgSleepMS,
|
||||
fAvgPresentGapMS,
|
||||
fAvgInterpolation,
|
||||
static_cast<unsigned long>(m_dwRenderTelemetryLoopCount),
|
||||
static_cast<unsigned long>(m_dwRenderTelemetryUpdateCount),
|
||||
static_cast<unsigned long>(m_dwRenderTelemetryRenderCount),
|
||||
static_cast<unsigned long>(m_dwRenderTelemetryBlockedRenderCount),
|
||||
static_cast<unsigned long>(m_dwCurUpdateTime),
|
||||
static_cast<unsigned long>(m_dwCurRenderTime));
|
||||
}
|
||||
|
||||
ResetRenderTelemetryWindow(dwNow);
|
||||
}
|
||||
|
||||
bool CPythonApplication::IsRenderTelemetrySamplingEnabled() const
|
||||
{
|
||||
return m_bRenderTelemetryEnabled || m_bRenderTelemetryHudVisible;
|
||||
}
|
||||
|
||||
void CPythonApplication::RenderPerformanceHUD()
|
||||
{
|
||||
if (!m_bRenderTelemetryHudVisible)
|
||||
return;
|
||||
|
||||
CGraphicText* pkDefaultFont = static_cast<CGraphicText*>(DefaultFont_GetResource());
|
||||
if (!pkDefaultFont)
|
||||
return;
|
||||
|
||||
m_kRenderTelemetryTextLine.SetTextPointer(pkDefaultFont);
|
||||
m_kRenderTelemetryTextLine.SetOutline(true);
|
||||
m_kRenderTelemetryTextLine.SetMultiLine(true);
|
||||
m_kRenderTelemetryTextLine.SetColor(1.0f, 1.0f, 1.0f);
|
||||
m_kRenderTelemetryTextLine.SetPosition(12.0f, 12.0f);
|
||||
m_kRenderTelemetryTextLine.SetValueString(m_stRenderTelemetrySummary);
|
||||
m_kRenderTelemetryTextLine.Update();
|
||||
m_kRenderTelemetryTextLine.Render();
|
||||
}
|
||||
|
||||
bool CPythonApplication::Process()
|
||||
{
|
||||
ELTimer_SetFrameMSec();
|
||||
|
||||
// m_Profiler.Clear();
|
||||
DWORD dwStart = ELTimer_GetMSec();
|
||||
|
||||
@@ -269,6 +507,18 @@ bool CPythonApplication::Process()
|
||||
static DWORD s_dwFaceCount = 0;
|
||||
static UINT s_uiLoad = 0;
|
||||
static DWORD s_dwCheckTime = ELTimer_GetMSec();
|
||||
static double s_fNextUpdateTime = static_cast<double>(ELTimer_GetMSec());
|
||||
static double s_fNextRenderTime = static_cast<double>(ELTimer_GetMSec());
|
||||
static double s_fFixedFrameTime = static_cast<double>(ELTimer_GetMSec());
|
||||
|
||||
DWORD dwCurrentTime = ELTimer_GetMSec();
|
||||
const double fCurrentTime = static_cast<double>(dwCurrentTime);
|
||||
const bool bSampleRenderTelemetry = IsRenderTelemetrySamplingEnabled();
|
||||
if (bSampleRenderTelemetry && !m_dwRenderTelemetryWindowStartMS)
|
||||
ResetRenderTelemetryWindow(dwCurrentTime);
|
||||
|
||||
if (bSampleRenderTelemetry)
|
||||
++m_dwRenderTelemetryLoopCount;
|
||||
|
||||
if (ELTimer_GetMSec() - s_dwCheckTime > 1000) [[unlikely]] {
|
||||
m_dwUpdateFPS = s_dwUpdateFrameCount;
|
||||
@@ -282,278 +532,314 @@ bool CPythonApplication::Process()
|
||||
s_uiLoad = s_dwFaceCount = s_dwUpdateFrameCount = s_dwRenderFrameCount = 0;
|
||||
}
|
||||
|
||||
// Update Time
|
||||
static BOOL s_bFrameSkip = false;
|
||||
static UINT s_uiNextFrameTime = ELTimer_GetMSec();
|
||||
|
||||
#ifdef __PERFORMANCE_CHECK__
|
||||
DWORD dwUpdateTime1=ELTimer_GetMSec();
|
||||
#endif
|
||||
CTimer& rkTimer=CTimer::Instance();
|
||||
rkTimer.Advance();
|
||||
|
||||
m_fGlobalTime = rkTimer.GetCurrentSecond();
|
||||
m_fGlobalElapsedTime = rkTimer.GetElapsedSecond();
|
||||
|
||||
UINT uiFrameTime = rkTimer.GetElapsedMilliecond();
|
||||
s_uiNextFrameTime += uiFrameTime; //17 - 1ÃÊ´ç 60fps±âÁØ.
|
||||
|
||||
DWORD updatestart = ELTimer_GetMSec();
|
||||
#ifdef __PERFORMANCE_CHECK__
|
||||
DWORD dwUpdateTime2=ELTimer_GetMSec();
|
||||
#endif
|
||||
// Network I/O
|
||||
m_pyNetworkStream.Process();
|
||||
//m_pyNetworkDatagram.Process();
|
||||
|
||||
m_kGuildMarkUploader.Process();
|
||||
|
||||
m_kGuildMarkDownloader.Process();
|
||||
m_kAccountConnector.Process();
|
||||
|
||||
#ifdef __PERFORMANCE_CHECK__
|
||||
DWORD dwUpdateTime3=ELTimer_GetMSec();
|
||||
#endif
|
||||
//////////////////////
|
||||
// Input Process
|
||||
// Keyboard
|
||||
UpdateKeyboard();
|
||||
#ifdef __PERFORMANCE_CHECK__
|
||||
DWORD dwUpdateTime4=ELTimer_GetMSec();
|
||||
#endif
|
||||
// Mouse
|
||||
POINT Point;
|
||||
if (GetCursorPos(&Point)) [[likely]] {
|
||||
ScreenToClient(m_hWnd, &Point);
|
||||
OnMouseMove(Point.x, Point.y);
|
||||
}
|
||||
//////////////////////
|
||||
#ifdef __PERFORMANCE_CHECK__
|
||||
DWORD dwUpdateTime5=ELTimer_GetMSec();
|
||||
#endif
|
||||
//!@# Alt+Tab Áß SetTransfor ¿¡¼ ƨ±è Çö»ó ÇØ°áÀ» À§ÇØ - [levites]
|
||||
//if (m_isActivateWnd)
|
||||
__UpdateCamera();
|
||||
#ifdef __PERFORMANCE_CHECK__
|
||||
DWORD dwUpdateTime6=ELTimer_GetMSec();
|
||||
#endif
|
||||
// Update Game Playing
|
||||
CResourceManager::Instance().Update();
|
||||
#ifdef __PERFORMANCE_CHECK__
|
||||
DWORD dwUpdateTime7=ELTimer_GetMSec();
|
||||
#endif
|
||||
OnCameraUpdate();
|
||||
#ifdef __PERFORMANCE_CHECK__
|
||||
DWORD dwUpdateTime8=ELTimer_GetMSec();
|
||||
#endif
|
||||
OnMouseUpdate();
|
||||
#ifdef __PERFORMANCE_CHECK__
|
||||
DWORD dwUpdateTime9=ELTimer_GetMSec();
|
||||
#endif
|
||||
OnUIUpdate();
|
||||
|
||||
#ifdef __PERFORMANCE_CHECK__
|
||||
DWORD dwUpdateTime10=ELTimer_GetMSec();
|
||||
|
||||
if (dwUpdateTime10-dwUpdateTime1>10)
|
||||
{
|
||||
static FILE* fp=fopen("perf_app_update.txt", "w");
|
||||
|
||||
fprintf(fp, "AU.Total %d (Time %d)\n", dwUpdateTime9-dwUpdateTime1, ELTimer_GetMSec());
|
||||
fprintf(fp, "AU.TU %d\n", dwUpdateTime2-dwUpdateTime1);
|
||||
fprintf(fp, "AU.NU %d\n", dwUpdateTime3-dwUpdateTime2);
|
||||
fprintf(fp, "AU.KU %d\n", dwUpdateTime4-dwUpdateTime3);
|
||||
fprintf(fp, "AU.MP %d\n", dwUpdateTime5-dwUpdateTime4);
|
||||
fprintf(fp, "AU.CP %d\n", dwUpdateTime6-dwUpdateTime5);
|
||||
fprintf(fp, "AU.RU %d\n", dwUpdateTime7-dwUpdateTime6);
|
||||
fprintf(fp, "AU.CU %d\n", dwUpdateTime8-dwUpdateTime7);
|
||||
fprintf(fp, "AU.MU %d\n", dwUpdateTime9-dwUpdateTime8);
|
||||
fprintf(fp, "AU.UU %d\n", dwUpdateTime10-dwUpdateTime9);
|
||||
fprintf(fp, "----------------------------------\n");
|
||||
fflush(fp);
|
||||
}
|
||||
#endif
|
||||
|
||||
//UpdateÇϴµ¥ °É¸°½Ã°£.delta°ª
|
||||
m_dwCurUpdateTime = ELTimer_GetMSec() - updatestart;
|
||||
|
||||
DWORD dwCurrentTime = ELTimer_GetMSec();
|
||||
BOOL bCurrentLateUpdate = FALSE;
|
||||
|
||||
s_bFrameSkip = false;
|
||||
|
||||
if (dwCurrentTime > s_uiNextFrameTime)
|
||||
if (fCurrentTime - s_fNextUpdateTime > kMaxCatchUpDelayMS)
|
||||
{
|
||||
int dt = dwCurrentTime - s_uiNextFrameTime;
|
||||
int nAdjustTime = ((float)dt / (float)uiFrameTime) * uiFrameTime;
|
||||
s_fNextUpdateTime = fCurrentTime;
|
||||
s_fFixedFrameTime = fCurrentTime;
|
||||
}
|
||||
|
||||
if ( dt >= 500 )
|
||||
if (fCurrentTime - s_fNextRenderTime > kMaxCatchUpDelayMS)
|
||||
s_fNextRenderTime = fCurrentTime;
|
||||
|
||||
int iUpdateCount = 0;
|
||||
while (fCurrentTime + 0.0001 >= s_fNextUpdateTime)
|
||||
{
|
||||
if (!m_isFrameSkipDisable && iUpdateCount >= 5)
|
||||
{
|
||||
s_uiNextFrameTime += nAdjustTime;
|
||||
printf("FrameSkip º¸Á¤ %d\n",nAdjustTime);
|
||||
CTimer::Instance().Adjust(nAdjustTime);
|
||||
s_fNextUpdateTime = fCurrentTime;
|
||||
s_fFixedFrameTime = fCurrentTime;
|
||||
break;
|
||||
}
|
||||
|
||||
s_bFrameSkip = true;
|
||||
bCurrentLateUpdate = TRUE;
|
||||
}
|
||||
++iUpdateCount;
|
||||
s_fNextUpdateTime += kFixedUpdateMS;
|
||||
s_fFixedFrameTime += kFixedUpdateMS;
|
||||
ELTimer_SetFrameMSecValue(static_cast<DWORD>(s_fFixedFrameTime));
|
||||
m_v3LastCenterPosition = m_v3CenterPosition;
|
||||
|
||||
//s_bFrameSkip = false;
|
||||
|
||||
//if (dwCurrentTime > s_uiNextFrameTime)
|
||||
//{
|
||||
// int dt = dwCurrentTime - s_uiNextFrameTime;
|
||||
|
||||
// //³Ê¹« ´Ê¾úÀ» °æ¿ì µû¶óÀâ´Â´Ù.
|
||||
// //±×¸®°í m_dwCurUpdateTime´Â deltaÀε¥ delta¶û absolute timeÀ̶û ºñ±³ÇÏ¸é ¾î¼Àڴ°Ü?
|
||||
// //if (dt >= 500 || m_dwCurUpdateTime > s_uiNextFrameTime)
|
||||
|
||||
// //±âÁ¸ÄÚµå´ë·Î Çϸé 0.5ÃÊ ÀÌÇÏ Â÷À̳ »óÅ·Πupdate°¡ Áö¼ÓµÇ¸é °è¼Ó rendering frame skip¹ß»ý
|
||||
// if (dt >= 500 || m_dwCurUpdateTime > s_uiNextFrameTime)
|
||||
// {
|
||||
// s_uiNextFrameTime += dt / uiFrameTime * uiFrameTime;
|
||||
// printf("FrameSkip º¸Á¤ %d\n", dt / uiFrameTime * uiFrameTime);
|
||||
// CTimer::Instance().Adjust((dt / uiFrameTime) * uiFrameTime);
|
||||
// s_bFrameSkip = true;
|
||||
// }
|
||||
//}
|
||||
|
||||
if (m_isFrameSkipDisable)
|
||||
s_bFrameSkip = false;
|
||||
|
||||
#ifdef __VTUNE__
|
||||
s_bFrameSkip = false;
|
||||
#ifdef __PERFORMANCE_CHECK__
|
||||
DWORD dwUpdateTime1=ELTimer_GetMSec();
|
||||
#endif
|
||||
if (!s_bFrameSkip)
|
||||
{
|
||||
// static double pos=0.0f;
|
||||
// CGrannyMaterial::TranslateSpecularMatrix(fabs(sin(pos)*0.005), fabs(cos(pos)*0.005), 0.0f);
|
||||
// pos+=0.01f;
|
||||
CTimer& rkTimer=CTimer::Instance();
|
||||
rkTimer.Advance();
|
||||
|
||||
m_fGlobalTime = rkTimer.GetCurrentSecond();
|
||||
m_fGlobalElapsedTime = rkTimer.GetElapsedSecond();
|
||||
|
||||
DWORD updatestart = ELTimer_GetMSec();
|
||||
#ifdef __PERFORMANCE_CHECK__
|
||||
DWORD dwUpdateTime2=ELTimer_GetMSec();
|
||||
#endif
|
||||
// Network I/O
|
||||
m_pyNetworkStream.Process();
|
||||
//m_pyNetworkDatagram.Process();
|
||||
|
||||
m_kGuildMarkUploader.Process();
|
||||
|
||||
m_kGuildMarkDownloader.Process();
|
||||
m_kAccountConnector.Process();
|
||||
|
||||
#ifdef __PERFORMANCE_CHECK__
|
||||
DWORD dwUpdateTime3=ELTimer_GetMSec();
|
||||
#endif
|
||||
//////////////////////
|
||||
// Input Process
|
||||
// Keyboard
|
||||
UpdateKeyboard();
|
||||
#ifdef __PERFORMANCE_CHECK__
|
||||
DWORD dwUpdateTime4=ELTimer_GetMSec();
|
||||
#endif
|
||||
// Mouse
|
||||
POINT Point;
|
||||
if (GetCursorPos(&Point)) [[likely]] {
|
||||
ScreenToClient(m_hWnd, &Point);
|
||||
OnMouseMove(Point.x, Point.y);
|
||||
}
|
||||
//////////////////////
|
||||
#ifdef __PERFORMANCE_CHECK__
|
||||
DWORD dwUpdateTime5=ELTimer_GetMSec();
|
||||
#endif
|
||||
//!@# Alt+Tab Áß SetTransfor ¿¡¼ ƨ±è Çö»ó ÇØ°áÀ» À§ÇØ - [levites]
|
||||
//if (m_isActivateWnd)
|
||||
__UpdateCamera();
|
||||
#ifdef __PERFORMANCE_CHECK__
|
||||
DWORD dwUpdateTime6=ELTimer_GetMSec();
|
||||
#endif
|
||||
// Update Game Playing
|
||||
CResourceManager::Instance().Update();
|
||||
#ifdef __PERFORMANCE_CHECK__
|
||||
DWORD dwUpdateTime7=ELTimer_GetMSec();
|
||||
#endif
|
||||
OnCameraUpdate();
|
||||
#ifdef __PERFORMANCE_CHECK__
|
||||
DWORD dwUpdateTime8=ELTimer_GetMSec();
|
||||
#endif
|
||||
OnMouseUpdate();
|
||||
#ifdef __PERFORMANCE_CHECK__
|
||||
DWORD dwUpdateTime9=ELTimer_GetMSec();
|
||||
#endif
|
||||
CGrannyMaterial::TranslateSpecularMatrix(g_specularSpd, g_specularSpd, 0.0f);
|
||||
OnUIUpdate();
|
||||
|
||||
DWORD dwRenderStartTime = ELTimer_GetMSec();
|
||||
#ifdef __PERFORMANCE_CHECK__
|
||||
DWORD dwUpdateTime10=ELTimer_GetMSec();
|
||||
|
||||
bool canRender = true;
|
||||
|
||||
if (m_isMinimizedWnd) [[unlikely]] {
|
||||
canRender = false;
|
||||
}
|
||||
else [[likely]] {
|
||||
if (m_pyGraphic.IsLostDevice()) [[unlikely]] {
|
||||
CPythonBackground& rkBG = CPythonBackground::Instance();
|
||||
rkBG.ReleaseCharacterShadowTexture();
|
||||
|
||||
if (m_pyGraphic.RestoreDevice())
|
||||
rkBG.CreateCharacterShadowTexture();
|
||||
else
|
||||
canRender = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (canRender) [[likely]]
|
||||
if (dwUpdateTime10-dwUpdateTime1>10)
|
||||
{
|
||||
// RestoreLostDevice
|
||||
CCullingManager::Instance().Update();
|
||||
if (m_pyGraphic.Begin()) [[likely]] {
|
||||
static FILE* fp=fopen("perf_app_update.txt", "w");
|
||||
|
||||
m_pyGraphic.ClearDepthBuffer();
|
||||
fprintf(fp, "AU.Total %d (Time %d)\n", dwUpdateTime9-dwUpdateTime1, ELTimer_GetMSec());
|
||||
fprintf(fp, "AU.TU %d\n", dwUpdateTime2-dwUpdateTime1);
|
||||
fprintf(fp, "AU.NU %d\n", dwUpdateTime3-dwUpdateTime2);
|
||||
fprintf(fp, "AU.KU %d\n", dwUpdateTime4-dwUpdateTime3);
|
||||
fprintf(fp, "AU.MP %d\n", dwUpdateTime5-dwUpdateTime4);
|
||||
fprintf(fp, "AU.CP %d\n", dwUpdateTime6-dwUpdateTime5);
|
||||
fprintf(fp, "AU.RU %d\n", dwUpdateTime7-dwUpdateTime6);
|
||||
fprintf(fp, "AU.CU %d\n", dwUpdateTime8-dwUpdateTime7);
|
||||
fprintf(fp, "AU.MU %d\n", dwUpdateTime9-dwUpdateTime8);
|
||||
fprintf(fp, "AU.UU %d\n", dwUpdateTime10-dwUpdateTime9);
|
||||
fprintf(fp, "----------------------------------\n");
|
||||
fflush(fp);
|
||||
}
|
||||
#endif
|
||||
|
||||
//UpdateÇϴµ¥ °É¸°½Ã°£.delta°ª
|
||||
m_dwCurUpdateTime = ELTimer_GetMSec() - updatestart;
|
||||
if (bSampleRenderTelemetry)
|
||||
{
|
||||
++m_dwRenderTelemetryUpdateCount;
|
||||
m_fRenderTelemetryUpdateTimeSumMS += static_cast<double>(m_dwCurUpdateTime);
|
||||
}
|
||||
++s_dwUpdateFrameCount;
|
||||
}
|
||||
|
||||
const double fPreviousUpdateTime = s_fNextUpdateTime - kFixedUpdateMS;
|
||||
m_fRenderInterpolationFactor = ClampInterpolationFactor(static_cast<float>((fCurrentTime - fPreviousUpdateTime) / kFixedUpdateMS));
|
||||
|
||||
DWORD dwRenderStartTime = ELTimer_GetMSec();
|
||||
bool canRender = true;
|
||||
bool didRender = false;
|
||||
|
||||
if (m_isMinimizedWnd) [[unlikely]] {
|
||||
canRender = false;
|
||||
}
|
||||
else [[likely]] {
|
||||
if (m_pyGraphic.IsLostDevice()) [[unlikely]] {
|
||||
CPythonBackground& rkBG = CPythonBackground::Instance();
|
||||
rkBG.ReleaseCharacterShadowTexture();
|
||||
|
||||
if (m_pyGraphic.RestoreDevice())
|
||||
rkBG.CreateCharacterShadowTexture();
|
||||
else
|
||||
canRender = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (canRender) [[likely]]
|
||||
{
|
||||
// RestoreLostDevice
|
||||
CCullingManager::Instance().Update();
|
||||
if (m_pyGraphic.Begin()) [[likely]] {
|
||||
|
||||
m_pyGraphic.ClearDepthBuffer();
|
||||
|
||||
#ifdef _DEBUG
|
||||
m_pyGraphic.SetClearColor(0.3f, 0.3f, 0.3f);
|
||||
m_pyGraphic.Clear();
|
||||
m_pyGraphic.SetClearColor(0.3f, 0.3f, 0.3f);
|
||||
m_pyGraphic.Clear();
|
||||
#endif
|
||||
|
||||
/////////////////////
|
||||
// Interface
|
||||
m_pyGraphic.SetInterfaceRenderState();
|
||||
/////////////////////
|
||||
// Interface
|
||||
m_pyGraphic.SetInterfaceRenderState();
|
||||
|
||||
OnUIRender();
|
||||
OnMouseRender();
|
||||
/////////////////////
|
||||
OnUIRender();
|
||||
RenderPerformanceHUD();
|
||||
OnMouseRender();
|
||||
/////////////////////
|
||||
|
||||
m_pyGraphic.End();
|
||||
m_pyGraphic.End();
|
||||
m_pyGraphic.Show();
|
||||
|
||||
//DWORD t1 = ELTimer_GetMSec();
|
||||
m_pyGraphic.Show();
|
||||
//DWORD t2 = ELTimer_GetMSec();
|
||||
DWORD dwRenderEndTime = ELTimer_GetMSec();
|
||||
didRender = true;
|
||||
|
||||
DWORD dwRenderEndTime = ELTimer_GetMSec();
|
||||
static DWORD s_dwRenderCheckTime = dwRenderEndTime;
|
||||
static DWORD s_dwRenderRangeTime = 0;
|
||||
static DWORD s_dwRenderRangeFrame = 0;
|
||||
|
||||
static DWORD s_dwRenderCheckTime = dwRenderEndTime;
|
||||
static DWORD s_dwRenderRangeTime = 0;
|
||||
static DWORD s_dwRenderRangeFrame = 0;
|
||||
m_dwCurRenderTime = dwRenderEndTime - dwRenderStartTime;
|
||||
s_dwRenderRangeTime += m_dwCurRenderTime;
|
||||
++s_dwRenderRangeFrame;
|
||||
|
||||
m_dwCurRenderTime = dwRenderEndTime - dwRenderStartTime;
|
||||
s_dwRenderRangeTime += m_dwCurRenderTime;
|
||||
++s_dwRenderRangeFrame;
|
||||
if (dwRenderEndTime-s_dwRenderCheckTime>1000) [[unlikely]] {
|
||||
m_fAveRenderTime=float(double(s_dwRenderRangeTime)/double(s_dwRenderRangeFrame));
|
||||
|
||||
if (dwRenderEndTime-s_dwRenderCheckTime>1000) [[unlikely]] {
|
||||
m_fAveRenderTime=float(double(s_dwRenderRangeTime)/double(s_dwRenderRangeFrame));
|
||||
s_dwRenderCheckTime=ELTimer_GetMSec();
|
||||
s_dwRenderRangeTime=0;
|
||||
s_dwRenderRangeFrame=0;
|
||||
}
|
||||
|
||||
s_dwRenderCheckTime=ELTimer_GetMSec();
|
||||
s_dwRenderRangeTime=0;
|
||||
s_dwRenderRangeFrame=0;
|
||||
}
|
||||
DWORD dwCurFaceCount=m_pyGraphic.GetFaceCount();
|
||||
m_pyGraphic.ResetFaceCount();
|
||||
s_dwFaceCount += dwCurFaceCount;
|
||||
|
||||
DWORD dwCurFaceCount=m_pyGraphic.GetFaceCount();
|
||||
m_pyGraphic.ResetFaceCount();
|
||||
s_dwFaceCount += dwCurFaceCount;
|
||||
if (dwCurFaceCount > 5000)
|
||||
{
|
||||
m_dwFaceAccCount += dwCurFaceCount;
|
||||
m_dwFaceAccTime += m_dwCurRenderTime;
|
||||
|
||||
if (dwCurFaceCount > 5000)
|
||||
m_fFaceSpd=(m_dwFaceAccCount/m_dwFaceAccTime);
|
||||
|
||||
// °Å¸® ÀÚµ¿ Á¶Àý
|
||||
if (-1 == m_iForceSightRange)
|
||||
{
|
||||
m_dwFaceAccCount += dwCurFaceCount;
|
||||
m_dwFaceAccTime += m_dwCurRenderTime;
|
||||
|
||||
m_fFaceSpd=(m_dwFaceAccCount/m_dwFaceAccTime);
|
||||
|
||||
// °Å¸® ÀÚµ¿ Á¶Àý
|
||||
if (-1 == m_iForceSightRange)
|
||||
{
|
||||
static float s_fAveRenderTime = 16.0f;
|
||||
float fRatio=0.3f;
|
||||
s_fAveRenderTime=(s_fAveRenderTime*(100.0f-fRatio)+std::max(16.0f, (float)m_dwCurRenderTime)*fRatio)/100.0f;
|
||||
static float s_fAveRenderTime = 16.0f;
|
||||
float fRatio=0.3f;
|
||||
s_fAveRenderTime=(s_fAveRenderTime*(100.0f-fRatio)+std::max(16.0f, (float)m_dwCurRenderTime)*fRatio)/100.0f;
|
||||
|
||||
|
||||
float fFar=25600.0f;
|
||||
float fNear=MIN_FOG;
|
||||
double dbAvePow=double(1000.0f/s_fAveRenderTime);
|
||||
double dbMaxPow=60.0;
|
||||
float fDistance=std::max((float)(fNear+(fFar-fNear)*(dbAvePow)/dbMaxPow), fNear);
|
||||
m_pyBackground.SetViewDistanceSet(0, fDistance);
|
||||
}
|
||||
// °Å¸® °Á¦ ¼³Á¤½Ã
|
||||
else
|
||||
{
|
||||
m_pyBackground.SetViewDistanceSet(0, float(m_iForceSightRange));
|
||||
}
|
||||
float fFar=25600.0f;
|
||||
float fNear=MIN_FOG;
|
||||
double dbAvePow=double(1000.0f/s_fAveRenderTime);
|
||||
double dbMaxPow=60.0;
|
||||
float fDistance=std::max((float)(fNear+(fFar-fNear)*(dbAvePow)/dbMaxPow), fNear);
|
||||
m_pyBackground.SetViewDistanceSet(0, fDistance);
|
||||
}
|
||||
// °Å¸® °Á¦ ¼³Á¤½Ã
|
||||
else
|
||||
{
|
||||
// 10000 Æú¸®°ï º¸´Ù ÀûÀ»¶§´Â °¡Àå ¸Ö¸® º¸ÀÌ°Ô ÇÑ´Ù
|
||||
m_pyBackground.SetViewDistanceSet(0, 25600.0f);
|
||||
m_pyBackground.SetViewDistanceSet(0, float(m_iForceSightRange));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 10000 Æú¸®°ï º¸´Ù ÀûÀ»¶§´Â °¡Àå ¸Ö¸® º¸ÀÌ°Ô ÇÑ´Ù
|
||||
m_pyBackground.SetViewDistanceSet(0, 25600.0f);
|
||||
}
|
||||
|
||||
++s_dwRenderFrameCount;
|
||||
|
||||
if (bSampleRenderTelemetry)
|
||||
{
|
||||
++m_dwRenderTelemetryRenderCount;
|
||||
m_fRenderTelemetryRenderTimeSumMS += static_cast<double>(m_dwCurRenderTime);
|
||||
m_fRenderTelemetryInterpolationSum += static_cast<double>(m_fRenderInterpolationFactor);
|
||||
|
||||
if (m_dwRenderTelemetryLastPresentTime)
|
||||
{
|
||||
m_fRenderTelemetryPresentGapSumMS += static_cast<double>(dwRenderEndTime - m_dwRenderTelemetryLastPresentTime);
|
||||
++m_dwRenderTelemetryPresentGapSamples;
|
||||
}
|
||||
|
||||
++s_dwRenderFrameCount;
|
||||
m_dwRenderTelemetryLastPresentTime = dwRenderEndTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int rest = s_uiNextFrameTime - ELTimer_GetMSec();
|
||||
if (bSampleRenderTelemetry && !didRender)
|
||||
++m_dwRenderTelemetryBlockedRenderCount;
|
||||
|
||||
if (rest > 0 && !bCurrentLateUpdate )
|
||||
if (m_iFPS > 0)
|
||||
{
|
||||
s_uiLoad -= rest; // ½® ½Ã°£Àº ·Îµå¿¡¼ »«´Ù..
|
||||
Sleep(rest);
|
||||
}
|
||||
const double fRenderFrameMS = 1000.0 / static_cast<double>(m_iFPS);
|
||||
s_fNextRenderTime += fRenderFrameMS;
|
||||
|
||||
++s_dwUpdateFrameCount;
|
||||
const double fSleepMS = s_fNextRenderTime - static_cast<double>(ELTimer_GetMSec());
|
||||
if (fSleepMS > 0.999)
|
||||
{
|
||||
const UINT uiSleepMS = static_cast<UINT>(fSleepMS);
|
||||
s_uiLoad -= std::min(s_uiLoad, uiSleepMS);
|
||||
const DWORD dwSleepStart = ELTimer_GetMSec();
|
||||
Sleep(static_cast<DWORD>(fSleepMS));
|
||||
if (bSampleRenderTelemetry)
|
||||
m_fRenderTelemetrySleepTimeSumMS += static_cast<double>(ELTimer_GetMSec() - dwSleepStart);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
s_fNextRenderTime = static_cast<double>(ELTimer_GetMSec());
|
||||
}
|
||||
|
||||
s_uiLoad += ELTimer_GetMSec() - dwStart;
|
||||
//m_Profiler.ProfileByScreen();
|
||||
|
||||
const DWORD dwTelemetryNow = ELTimer_GetMSec();
|
||||
if (bSampleRenderTelemetry && dwTelemetryNow - m_dwRenderTelemetryWindowStartMS >= m_dwRenderTelemetryIntervalMS)
|
||||
FlushRenderTelemetryWindow(dwTelemetryNow);
|
||||
|
||||
//m_Profiler.ProfileByScreen();
|
||||
return true;
|
||||
}
|
||||
|
||||
void CPythonApplication::ApplyRenderInterpolation()
|
||||
{
|
||||
const float fInterpolation = ClampInterpolationFactor(m_fRenderInterpolationFactor);
|
||||
|
||||
m_kChrMgr.ApplyRenderInterpolation(fInterpolation);
|
||||
|
||||
if (CAMERA_MODE_NORMAL != m_iCameraMode)
|
||||
return;
|
||||
|
||||
if (0.0f != m_kCmrPos.m_fViewDir || 0.0f != m_kCmrPos.m_fCrossDir || 0.0f != m_kCmrPos.m_fUpDir)
|
||||
return;
|
||||
|
||||
CCamera* pCurrentCamera = CCameraManager::Instance().GetCurrentCamera();
|
||||
if (!pCurrentCamera)
|
||||
return;
|
||||
|
||||
const D3DXVECTOR3 v3CenterPosition = LerpVector3(m_v3LastCenterPosition, m_v3CenterPosition, fInterpolation);
|
||||
const float fDistance = pCurrentCamera->GetDistance();
|
||||
const float fPitch = pCurrentCamera->GetPitch();
|
||||
const float fRotation = pCurrentCamera->GetRoll();
|
||||
|
||||
m_pyGraphic.SetPositionCamera(
|
||||
v3CenterPosition.x,
|
||||
v3CenterPosition.y,
|
||||
v3CenterPosition.z + pCurrentCamera->GetTargetHeight(),
|
||||
fDistance,
|
||||
fPitch,
|
||||
fRotation);
|
||||
}
|
||||
|
||||
void CPythonApplication::UpdateClientRect()
|
||||
{
|
||||
RECT rcApp;
|
||||
@@ -1023,7 +1309,39 @@ float CPythonApplication::GetGlobalElapsedTime()
|
||||
|
||||
void CPythonApplication::SetFPS(int iFPS)
|
||||
{
|
||||
m_iFPS = iFPS;
|
||||
m_iFPS = std::max(0, iFPS);
|
||||
char szTargetFPS[16];
|
||||
FormatRenderTargetFPSLabel(m_iFPS, szTargetFPS, sizeof(szTargetFPS));
|
||||
m_stRenderTelemetrySummary = "Render telemetry\nTarget FPS: ";
|
||||
m_stRenderTelemetrySummary += szTargetFPS;
|
||||
m_stRenderTelemetrySummary += "\nCollecting frame pacing...";
|
||||
if (IsRenderTelemetrySamplingEnabled())
|
||||
ResetRenderTelemetryWindow(ELTimer_GetMSec());
|
||||
|
||||
if (m_bRenderTelemetryEnabled)
|
||||
{
|
||||
AppendRenderTelemetryTrace(
|
||||
"set_fps elapsed_ms=%lu target_fps=%u",
|
||||
static_cast<unsigned long>(ELTimer_GetMSec() - m_dwStartLocalTime),
|
||||
m_iFPS);
|
||||
}
|
||||
}
|
||||
|
||||
void CPythonApplication::SetPerformanceHUDVisible(bool isVisible)
|
||||
{
|
||||
m_bRenderTelemetryHudVisible = isVisible;
|
||||
if (IsRenderTelemetrySamplingEnabled())
|
||||
ResetRenderTelemetryWindow(ELTimer_GetMSec());
|
||||
else
|
||||
m_dwRenderTelemetryWindowStartMS = 0;
|
||||
|
||||
if (m_bRenderTelemetryEnabled)
|
||||
{
|
||||
AppendRenderTelemetryTrace(
|
||||
"set_hud elapsed_ms=%lu visible=%u",
|
||||
static_cast<unsigned long>(ELTimer_GetMSec() - m_dwStartLocalTime),
|
||||
m_bRenderTelemetryHudVisible ? 1u : 0u);
|
||||
}
|
||||
}
|
||||
|
||||
int CPythonApplication::GetWidth()
|
||||
@@ -1075,6 +1393,7 @@ void CPythonApplication::Destroy()
|
||||
// SphereMap
|
||||
CGrannyMaterial::DestroySphereMap();
|
||||
|
||||
m_kRenderTelemetryTextLine.Destroy();
|
||||
m_kWndMgr.Destroy();
|
||||
|
||||
CPythonSystem::Instance().SaveConfig();
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "eterLib/Input.h"
|
||||
#include "eterLib/Profiler.h"
|
||||
#include "eterLib/GrpDevice.h"
|
||||
#include "EterLib/GrpTextInstance.h"
|
||||
#include "eterLib/NetDevice.h"
|
||||
#include "eterLib/GrpLightManager.h"
|
||||
#include "eterLib/GameThreadPool.h"
|
||||
@@ -42,7 +43,13 @@
|
||||
#include "AbstractApplication.h"
|
||||
#include "MovieMan.h"
|
||||
|
||||
#include <qedit.h>
|
||||
struct IGraphBuilder;
|
||||
struct IBaseFilter;
|
||||
struct ISampleGrabber;
|
||||
struct IMediaControl;
|
||||
struct IMediaEventEx;
|
||||
struct IVideoWindow;
|
||||
struct IBasicVideo;
|
||||
|
||||
class CPythonApplication : public CMSApplication, public CInputKeyboard, public IAbstractApplication
|
||||
{
|
||||
@@ -206,6 +213,7 @@ class CPythonApplication : public CMSApplication, public CInputKeyboard, public
|
||||
float GetPitch();
|
||||
|
||||
void SetFPS(int iFPS);
|
||||
void SetPerformanceHUDVisible(bool isVisible);
|
||||
void SetServerTime(time_t tTime);
|
||||
time_t GetServerTime();
|
||||
time_t GetServerTimeStamp();
|
||||
@@ -303,6 +311,12 @@ class CPythonApplication : public CMSApplication, public CInputKeyboard, public
|
||||
BOOL __IsContinuousChangeTypeCursor(int iCursorNum);
|
||||
|
||||
void __UpdateCamera();
|
||||
void ApplyRenderInterpolation();
|
||||
bool IsRenderTelemetrySamplingEnabled() const;
|
||||
void InitializeRenderRuntimeOverrides();
|
||||
void ResetRenderTelemetryWindow(DWORD dwNow);
|
||||
void FlushRenderTelemetryWindow(DWORD dwNow);
|
||||
void RenderPerformanceHUD();
|
||||
|
||||
void __SetFullScreenWindow(HWND hWnd, DWORD dwWidth, DWORD dwHeight, DWORD dwBPP);
|
||||
void __MinimizeFullScreenWindow(HWND hWnd, DWORD dwWidth, DWORD dwHeight);
|
||||
@@ -357,9 +371,28 @@ class CPythonApplication : public CMSApplication, public CInputKeyboard, public
|
||||
|
||||
PyObject * m_poMouseHandler;
|
||||
D3DXVECTOR3 m_v3CenterPosition;
|
||||
D3DXVECTOR3 m_v3LastCenterPosition;
|
||||
CGraphicTextInstance m_kRenderTelemetryTextLine;
|
||||
std::string m_stRenderTelemetrySummary;
|
||||
|
||||
unsigned int m_iFPS;
|
||||
float m_fRenderInterpolationFactor;
|
||||
float m_fAveRenderTime;
|
||||
bool m_bRenderTelemetryEnabled;
|
||||
bool m_bRenderTelemetryHudVisible;
|
||||
DWORD m_dwRenderTelemetryIntervalMS;
|
||||
DWORD m_dwRenderTelemetryWindowStartMS;
|
||||
DWORD m_dwRenderTelemetryLoopCount;
|
||||
DWORD m_dwRenderTelemetryUpdateCount;
|
||||
DWORD m_dwRenderTelemetryRenderCount;
|
||||
DWORD m_dwRenderTelemetryBlockedRenderCount;
|
||||
DWORD m_dwRenderTelemetryLastPresentTime;
|
||||
DWORD m_dwRenderTelemetryPresentGapSamples;
|
||||
double m_fRenderTelemetryUpdateTimeSumMS;
|
||||
double m_fRenderTelemetryRenderTimeSumMS;
|
||||
double m_fRenderTelemetrySleepTimeSumMS;
|
||||
double m_fRenderTelemetryInterpolationSum;
|
||||
double m_fRenderTelemetryPresentGapSumMS;
|
||||
DWORD m_dwCurRenderTime;
|
||||
DWORD m_dwCurUpdateTime;
|
||||
DWORD m_dwLoad;
|
||||
|
||||
@@ -358,6 +358,34 @@ struct FCharacterManagerCharacterInstanceDeform
|
||||
//pInstance->Update();
|
||||
}
|
||||
};
|
||||
struct FCharacterManagerCharacterInstanceApplyRenderInterpolation
|
||||
{
|
||||
explicit FCharacterManagerCharacterInstanceApplyRenderInterpolation(float fInterpolation)
|
||||
: m_fInterpolation(fInterpolation)
|
||||
{
|
||||
}
|
||||
|
||||
inline void operator () (const std::pair<DWORD, CInstanceBase*>& cr_Pair)
|
||||
{
|
||||
cr_Pair.second->ApplyRenderInterpolation(m_fInterpolation);
|
||||
}
|
||||
|
||||
float m_fInterpolation;
|
||||
};
|
||||
struct FCharacterManagerCharacterInstanceListApplyRenderInterpolation
|
||||
{
|
||||
explicit FCharacterManagerCharacterInstanceListApplyRenderInterpolation(float fInterpolation)
|
||||
: m_fInterpolation(fInterpolation)
|
||||
{
|
||||
}
|
||||
|
||||
inline void operator () (CInstanceBase* pInstance)
|
||||
{
|
||||
pInstance->ApplyRenderInterpolation(m_fInterpolation);
|
||||
}
|
||||
|
||||
float m_fInterpolation;
|
||||
};
|
||||
struct FCharacterManagerCharacterInstanceListDeform
|
||||
{
|
||||
inline void operator () (CInstanceBase * pInstance)
|
||||
@@ -366,6 +394,12 @@ struct FCharacterManagerCharacterInstanceListDeform
|
||||
}
|
||||
};
|
||||
|
||||
void CPythonCharacterManager::ApplyRenderInterpolation(float fInterpolation)
|
||||
{
|
||||
std::for_each(m_kAliveInstMap.begin(), m_kAliveInstMap.end(), FCharacterManagerCharacterInstanceApplyRenderInterpolation(fInterpolation));
|
||||
std::for_each(m_kDeadInstList.begin(), m_kDeadInstList.end(), FCharacterManagerCharacterInstanceListApplyRenderInterpolation(fInterpolation));
|
||||
}
|
||||
|
||||
void CPythonCharacterManager::Deform()
|
||||
{
|
||||
std::for_each(m_kAliveInstMap.begin(), m_kAliveInstMap.end(), FCharacterManagerCharacterInstanceDeform());
|
||||
|
||||
@@ -57,6 +57,7 @@ class CPythonCharacterManager : public CSingleton<CPythonCharacterManager>, publ
|
||||
void DestroyDeviceObjects();
|
||||
|
||||
void Update();
|
||||
void ApplyRenderInterpolation(float fInterpolation);
|
||||
void Deform();
|
||||
void Render();
|
||||
void RenderShadowMainInstance();
|
||||
|
||||
@@ -318,6 +318,8 @@ void CPythonSystem::SetDefaultConfig()
|
||||
m_Config.bAlwaysShowName = DEFAULT_VALUE_ALWAYS_SHOW_NAME;
|
||||
m_Config.bShowDamage = true;
|
||||
m_Config.bShowSalesText = true;
|
||||
m_Config.iRenderFPS = 60;
|
||||
m_Config.bShowPerformanceHUD = false;
|
||||
}
|
||||
|
||||
bool CPythonSystem::IsWindowed()
|
||||
@@ -365,6 +367,26 @@ void CPythonSystem::SetShowSalesTextFlag(int iFlag)
|
||||
m_Config.bShowSalesText = iFlag == 1 ? true : false;
|
||||
}
|
||||
|
||||
int CPythonSystem::GetRenderFPS()
|
||||
{
|
||||
return m_Config.iRenderFPS;
|
||||
}
|
||||
|
||||
void CPythonSystem::SetRenderFPS(int iFPS)
|
||||
{
|
||||
m_Config.iRenderFPS = std::max(0, std::min(iFPS, 500));
|
||||
}
|
||||
|
||||
bool CPythonSystem::IsShowPerformanceHUD()
|
||||
{
|
||||
return m_Config.bShowPerformanceHUD;
|
||||
}
|
||||
|
||||
void CPythonSystem::SetShowPerformanceHUDFlag(int iFlag)
|
||||
{
|
||||
m_Config.bShowPerformanceHUD = iFlag == 1 ? true : false;
|
||||
}
|
||||
|
||||
bool CPythonSystem::IsAutoTiling()
|
||||
{
|
||||
if (m_Config.bSoftwareTiling == 0)
|
||||
@@ -462,6 +484,10 @@ bool CPythonSystem::LoadConfig()
|
||||
m_Config.bShowDamage = atoi(value) == 1 ? true : false;
|
||||
else if (!stricmp(command, "SHOW_SALESTEXT"))
|
||||
m_Config.bShowSalesText = atoi(value) == 1 ? true : false;
|
||||
else if (!stricmp(command, "RENDER_FPS"))
|
||||
m_Config.iRenderFPS = std::max(0, std::min(atoi(value), 500));
|
||||
else if (!stricmp(command, "SHOW_PERFORMANCE_HUD"))
|
||||
m_Config.bShowPerformanceHUD = atoi(value) == 1 ? true : false;
|
||||
}
|
||||
|
||||
if (m_Config.bWindowed)
|
||||
@@ -552,6 +578,8 @@ bool CPythonSystem::SaveConfig()
|
||||
fprintf(fp, "USE_DEFAULT_IME %d\n", m_Config.bUseDefaultIME);
|
||||
fprintf(fp, "SOFTWARE_TILING %d\n", m_Config.bSoftwareTiling);
|
||||
fprintf(fp, "SHADOW_LEVEL %d\n", m_Config.iShadowLevel);
|
||||
fprintf(fp, "RENDER_FPS %d\n", m_Config.iRenderFPS);
|
||||
fprintf(fp, "SHOW_PERFORMANCE_HUD %d\n", m_Config.bShowPerformanceHUD);
|
||||
// MR-14: Fog update by Alaric
|
||||
fprintf(fp, "FOG_LEVEL %d\n", m_Config.iFogLevel);
|
||||
// MR-14: -- END OF -- Fog update by Alaric
|
||||
@@ -607,6 +635,9 @@ const CPythonSystem::TWindowStatus & CPythonSystem::GetWindowStatusReference(int
|
||||
|
||||
void CPythonSystem::ApplyConfig() // 이전 설정과 현재 설정을 비교해서 바뀐 설정을 적용 한다.
|
||||
{
|
||||
const bool bRenderFPSChanged = m_OldConfig.iRenderFPS != m_Config.iRenderFPS;
|
||||
const bool bPerformanceHUDChanged = m_OldConfig.bShowPerformanceHUD != m_Config.bShowPerformanceHUD;
|
||||
|
||||
if (m_OldConfig.gamma != m_Config.gamma)
|
||||
{
|
||||
float val = 1.0f;
|
||||
@@ -631,6 +662,12 @@ void CPythonSystem::ApplyConfig() // 이전 설정과 현재 설정을 비교해
|
||||
CPythonApplication::Instance().SetCursorMode(CPythonApplication::CURSOR_MODE_HARDWARE);
|
||||
}
|
||||
|
||||
if (bRenderFPSChanged)
|
||||
CPythonApplication::Instance().SetFPS(m_Config.iRenderFPS);
|
||||
|
||||
if (bPerformanceHUDChanged)
|
||||
CPythonApplication::Instance().SetPerformanceHUDVisible(m_Config.bShowPerformanceHUD);
|
||||
|
||||
m_OldConfig = m_Config;
|
||||
|
||||
ChangeSystem();
|
||||
|
||||
@@ -78,6 +78,8 @@ class CPythonSystem : public CSingleton<CPythonSystem>
|
||||
bool bAlwaysShowName;
|
||||
bool bShowDamage;
|
||||
bool bShowSalesText;
|
||||
int iRenderFPS;
|
||||
bool bShowPerformanceHUD;
|
||||
} TConfig;
|
||||
|
||||
public:
|
||||
@@ -157,6 +159,11 @@ class CPythonSystem : public CSingleton<CPythonSystem>
|
||||
void SetFogLevel(unsigned int level);
|
||||
// MR-14: -- END OF -- Fog update by Alaric
|
||||
|
||||
int GetRenderFPS();
|
||||
void SetRenderFPS(int iFPS);
|
||||
bool IsShowPerformanceHUD();
|
||||
void SetShowPerformanceHUDFlag(int iFlag);
|
||||
|
||||
protected:
|
||||
TResolution m_ResolutionList[RESOLUTION_MAX_NUM];
|
||||
int m_ResolutionCount;
|
||||
@@ -167,4 +174,4 @@ class CPythonSystem : public CSingleton<CPythonSystem>
|
||||
bool m_isInterfaceConfig;
|
||||
PyObject * m_poInterfaceHandler;
|
||||
TWindowStatus m_WindowStatus[WINDOW_MAX_NUM];
|
||||
};
|
||||
};
|
||||
|
||||
@@ -226,6 +226,36 @@ PyObject * systemIsShowSalesText(PyObject * poSelf, PyObject * poArgs)
|
||||
return Py_BuildValue("i", CPythonSystem::Instance().IsShowSalesText());
|
||||
}
|
||||
|
||||
PyObject * systemGetRenderFPS(PyObject * poSelf, PyObject * poArgs)
|
||||
{
|
||||
return Py_BuildValue("i", CPythonSystem::Instance().GetRenderFPS());
|
||||
}
|
||||
|
||||
PyObject * systemSetRenderFPS(PyObject * poSelf, PyObject * poArgs)
|
||||
{
|
||||
int iFPS;
|
||||
if (!PyTuple_GetInteger(poArgs, 0, &iFPS))
|
||||
return Py_BuildException();
|
||||
|
||||
CPythonSystem::Instance().SetRenderFPS(iFPS);
|
||||
return Py_BuildNone();
|
||||
}
|
||||
|
||||
PyObject * systemIsShowPerformanceHUD(PyObject * poSelf, PyObject * poArgs)
|
||||
{
|
||||
return Py_BuildValue("i", CPythonSystem::Instance().IsShowPerformanceHUD());
|
||||
}
|
||||
|
||||
PyObject * systemSetShowPerformanceHUDFlag(PyObject * poSelf, PyObject * poArgs)
|
||||
{
|
||||
int iFlag;
|
||||
if (!PyTuple_GetInteger(poArgs, 0, &iFlag))
|
||||
return Py_BuildException();
|
||||
|
||||
CPythonSystem::Instance().SetShowPerformanceHUDFlag(iFlag);
|
||||
return Py_BuildNone();
|
||||
}
|
||||
|
||||
PyObject * systemSetConfig(PyObject * poSelf, PyObject * poArgs)
|
||||
{
|
||||
int res_index;
|
||||
@@ -445,6 +475,11 @@ void initsystem()
|
||||
{ "SetShowSalesTextFlag", systemSetShowSalesTextFlag, METH_VARARGS },
|
||||
{ "IsShowSalesText", systemIsShowSalesText, METH_VARARGS },
|
||||
|
||||
{ "GetRenderFPS", systemGetRenderFPS, METH_VARARGS },
|
||||
{ "SetRenderFPS", systemSetRenderFPS, METH_VARARGS },
|
||||
{ "IsShowPerformanceHUD", systemIsShowPerformanceHUD, METH_VARARGS },
|
||||
{ "SetShowPerformanceHUDFlag", systemSetShowPerformanceHUDFlag, METH_VARARGS },
|
||||
|
||||
{ "GetShadowLevel", systemGetShadowLevel, METH_VARARGS },
|
||||
{ "SetShadowLevel", systemSetShadowLevel, METH_VARARGS },
|
||||
|
||||
|
||||
Reference in New Issue
Block a user